# 32.关于Block你所该知道的一切

### 一、 block 的分类

`全局 block` 、 `堆 block` 、 `栈 block` 。

#### 1.1 **NSGlobalBlock** 全局 block

* 位于全局区
* 未捕获任何变量 或 只捕获全局静态变量

**未捕获任何变量**

![1](/files/-MkHRIdWkn1LXUpaMSnZ)

```
<__NSGlobalBlock__: 0x100004030>
```

**只捕获全局静态变量**

![4](/files/-MkHRIdZ5zz0zgI2zoFg)

#### 1.2 堆 block

* 位于堆区
* 在 `block` 内部使用 `局部变量` 或者 `OC属性` ，并且赋值给 `强引用` 或者 `Copy修饰` 的变量

![2](/files/-MkHRId_TCt-DFjUm_Yn)

```
<__NSMallocBlock__: 0x1007049e0>
```

#### 1.3 栈 block

* 位于栈区
* 在 `block` 内部使用 `局部变量` 或者 `OC属性` ，但 `不` 赋值给 `强引用` 或者 `Copy修饰` 的变量

![3](/files/-MkHRIdaTYqLoWlD7aKr)

```
<__NSStackBlock__: 0x7ffeefbff378>
```

#### 1.4 面试题（一）

下面输出的结果会是什么样？

```objc
NSObject *obj = [NSObject new];
NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));

void (^blockA)(void) = ^{
    NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockA();

void (^ __weak blockB)(void) = ^{
    NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockB();

void (^blockC)(void) = [blockB copy];
blockC();
```

这里涉及到关于 `block类型` 的底层知识，我们到最后在进行解析。

#### 1.5 面试题（二）

下面输出的结果会是什么样？

```objc
int num = 0;
void (^ __weak blockA)(void) = nil;
{
    void (^ __weak blockB)(void) = ^{
        NSLog(@"%d", num);
    };
    blockA = blockB;
    NSLog(@"=");
}
blockA();
```

#### 1.6 面试题（三）

下面输出的结果会是什么样？

```objc
int num = 0;
void (^ __weak blockA)(void) = nil;
{
    void (^ blockB)(void) = ^{
        NSLog(@"%d", num);
    };
    blockA = blockB;
    NSLog(@"=");
}
blockA();
```

### 二、 堆、栈block的生命周期

这里结合 `二、三面试题` 来进行探索。

#### 2.1 面试题二解读

很可能有同学会给出只会打印 `=` 的答案。这就是对于 `堆、栈block的生命周期` 理解的不够深入。正确的输出如下

![5](/files/-MkHRIdcG-czONek1hnc)

为什么呢？关键是对 `blockB` 的生命周期理解错误。一般会这么理解：

![6](/files/-MkHRIddwXEI4qA6HGXq)

这里先不讲为什么，我们再看下 `面试题三`。

#### 2.2 面试题三解读

![7](/files/-MkHRIdeM6OeMx5Egq1c)

这里发生了野指针访问崩溃了。

#### 2.3 堆 block 的生命周期

![8](/files/-MkHRIdhonHUf4ZolnRl)

在大括号内部下断点可以看到， 这时 blockB 为 `堆block` 且有值。

![9](/files/-MkHRIdivEkc_CTmuQ47)

赋值给 `blockA` 。

![10](/files/-MkHRIdj9MpnE4BLARPt)

出了大括号后 `blockA` 为空了，说明 `blockB` 释放掉了。

> 因为作为 `存储在堆上` 的 `堆block` 其生命周期仅存在于大括号内部。

#### 2.4 栈 block 的生命周期

那么 `面试题二` 中的 `blockB` 的生命周期又是怎样的呢？

![11](/files/-MkHRIdlsxsnepYEJ5jl)

`blockB` 为 `栈block` 。

![12](/files/-MkHRIdmVtaNBXnwMNSR)

赋值给 `blockA` 。

![13](/files/-MkHRIdnmML8acbTkcLV)

出了大括号的作用域了， `blockA` 的地址依旧没变，说明 `blockB` 没有被释放。

> 因为作为 `存储在栈中` 的 `栈block` 其生命周期与 `调用栈` 相同。函数调用栈结束后释放。

### 三、 捕获变量的底层实现

#### 3.1 普通变量的捕获

```objc
int num = 1;
void (^block)(void) = ^{
    NSLog(@"%d", num);
};
block();
```

我们将上面的代码还原成 `C++` ：

![14](/files/-MkHRIdpJphLX8Loposh)

```
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
```

* `block` 的本质是一个结构体 `__main_block_impl_0` ，如果有需要捕获的变量，结构体内部会生成一个成员用来存储，例子中是 `int num;`
* `__main_block_func_0` 保存的是 `block` 的具体需要执行的函数
* `__block_impl` 是 `block` 的具体具体的一些数据，如函数等
* `__main_block_desc_0` 是描述 `block` 特征的一个数据结构

这段代码中做了些什么呢？

> 一、 创建 `block` ：
>
> 1、 调用 `__main_block_impl_0` 的 `构造函数`
>
> 2、 传入 `__main_block_func_0 函数` 和 `__main_block_desc_0_DATA 的地址` 和 `变量 num`
>
> 3、 指定 block 类型：`impl.isa = &_NSConcreteStackBlock;` （这里的堆block对应的是 `_NSConcreteStackBlock` ）
>
> 4、 存储标志位： `impl.Flags = flags;`
>
> 5、 存储函数： `impl.FuncPtr = fp;`
>
> 6、 描述内容赋值： `Desc = desc;`
>
> 二、 执行 `block` ：
>
> 1、 获取 `__main_block_func_0` 函数： `((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)`
>
> 2、 将 `block` 作为参数传入： `((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);`
>
> 三、 执行函数 `__main_block_func_0` ：
>
> 1、 从 `__main_block_impl_0` 中取出变量 `num` ： `int num = __cself->num;`
>
> 2、 执行 `NSLog` ： `NSLog((NSString *)&__NSConstantStringImpl__var_folders_py_7v1wvf813z5bmw0hr97yplwc0000gn_T_main_e543c2_mi_1, num);`

#### 3.2 \_\_block 普通变量的捕获

```
__block int num = 0;
void (^block)(void) = ^{
    num ++;
    NSLog(@"%d", num);
};
block();
```

将上面的代码转为 `C++`

![15](/files/-MkHRIdteusLcrGtGEwB)

我们发现多了几种数据类型： `__Block_byref_num_0` 、 `__main_block_copy_0` 、 `__main_block_dispose_0` 。

加了 `__block` 的变量，这里被用 `__Block_byref_num_0` 数据结构包装了一下，这里传入的 num 也是通过地址的形式传入的。

#### 3.3 捕获实例变量

不活实例变量和 `3.2` 没有什么区别。

```objc
NSNumber *num = @100;
void (^block)(void) = ^{
    NSLog(@"%@", num);
};
block();
```

![16](/files/-MkHRIduLtKemtWNceZf)

#### 3.4 全局 block

```objc
static const NSString *string = @"aaaaaaa";
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^{
            NSLog(@"%@", string);
        };
        block();
    }
}
```

![17](/files/-MkHRIdvA-qwe0ObxBRp)

#### 3.5 栈 block

```objc
int num = 0;
void (^ __weak block)(void) = ^{
    NSLog(@"%d", num);
};
block();
```

这里还原 C++ 你可能会遇到包错：

```
error: cannot create __weak reference because the current deployment target does not support weak references
        void (^ __attribute__((objc_ownership(weak))) block)(void)
```

加上一些参数即可：

```
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 main.m -o main.cpp
```

![18](/files/-MkHRIdwFIFZ5evKY_YN)

![19](/files/-MkHRIdxIuhh7Z0-DP6U)

这里发现 `__weak` 实际是用 `__attribute__((objc_ownership(weak)))` 进行了包装，这里还没有什么更有用的线索，我们先继续探究。

#### 3.6 \_NSConcreteStackBlock

相信细心的你一定能发现一个问题，不论什么类型的 `block` ，还原出的 `C++` 底层源码中都是 `impl.isa = &_NSConcreteStackBlock;` 看名字是个 `栈 block` 。和实际运行打断点运行时看到的不一样啊。下面我们就来分析一下这里发生了什么。

### 四、 运行时 block 类型的确定

由于这里并没有什么具体线索，我们通过下断点，并打开汇编。这里是一个 `全局block` 。

![20](/files/-MkHRIdzMDVTtNl34tVN)

#### 4.1 进入汇编断点

![21](/files/-MkHRIe-2f2qPRZNk_AJ)

#### 4.2 objc\_retainBlock

![22](/files/-MkHRIe0ZQXDSEb9mVms)

通过 ***libobjc.A.dylib\`objc\_retainBlock:*** 发现它在 ***libobjc*** 中。

![23](/files/-MkHRIe2_pFQPu-Lo1B7)

在 oc 源码中定位到了相关代码，但更进一步调用的 `_Block_copy` 并没有找到，我们继续回到汇编，继续执行下一步。

#### 4.3 \_Block\_copy

![24](/files/-MkHRIe3Rm8LpcY59cxW)

这例进入了 `_Block_copy` 的汇编，发现它是 `libsystem_blocks` 中的，我们来 `libclosure` 中寻找一下线索，找到了下面的源码。

```
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        /// 根据标志位判断，需要释放
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        /// 根据标志位判断，为一个 全局block 直接返回
        return aBlock;
    }
    else {
        // 是一个 堆 block 就进行 copy 开辟空间的操作，拷贝到了堆上
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        // 设置 isa 为 堆block
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}
```

### 五、 汇编验证 block 的类型转换

#### 5.1 全局block

![25](/files/-MkHRIe4vTJFa_09w4AW)

一直来到 `_Block_copy` 内，读取 `x0 寄存器` （ARM64下，x0这时存的是消息的接收者）。

![26](/files/-MkHRIe5nH2Xd2-zn-qw)

进入 `_Block_copy` 时，就已经标识为 全局block 了。

我们发现输出了这样数据结构 `signature` `invoke` ，后面再具体看他们代表了什么。

```
<__NSGlobalBlock__: 0x1006f8028>
 signature: "v8@?0"
 invoke   : 0x1006f5edc (/private/var/containers/Bundle/Application/49DA1CBF-A462-4433-80DE-19721201954A/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
```

#### 5.2 堆block

![27](/files/-MkHRIe6IGqMxWvt3pzy)

进入 `_Block_copy` 时，显示的是 栈block 。

![28](/files/-MkHRIe7EhhaLhOlWuUH)

```
<__NSStackBlock__: 0x16d6651b8>
 signature: "v8@?0"
 invoke   : 0x10279de90 (/private/var/containers/Bundle/Application/A3A281E1-1993-4E28-8F30-3EED05043A20/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
```

和源码所写的一样，这里进行了 malloc ：

![29](/files/-MkHRIe8sflaiJ0BLvKC)

在 `_Block_copy` `return` 之前我们再看一下 x0 ：

![30](/files/-MkHRIe9k8oE2EqPEc7T)

这里已经变成了 堆block

```
<__NSMallocBlock__: 0x281030ab0>
 signature: "v8@?0"
 invoke   : 0x10279de90 (/private/var/containers/Bundle/Application/A3A281E1-1993-4E28-8F30-3EED05043A20/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
```

**\_block 捕获变量的变化**

进入 `_Block_copy` 时：

![31](/files/-MkHRIeALcD7jg-ztFKc)

```
<__NSStackBlock__: 0x16b21d1a0>
 signature: "v8@?0"
 invoke   : 0x104be5da0 (/private/var/containers/Bundle/Application/76397764-704E-4CA4-80F3-D94500849B81/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
 copy     : 0x104be5e04 (/private/var/containers/Bundle/Application/76397764-704E-4CA4-80F3-D94500849B81/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__copy_helper_block_e8_32r)
 dispose  : 0x104be5e3c (/private/var/containers/Bundle/Application/76397764-704E-4CA4-80F3-D94500849B81/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__destroy_helper_block_e8_32r)
```

在 `_Block_copy` `return` 之前

![32](/files/-MkHRIeBa8Lf1plnE9nq)

```
<__NSMallocBlock__: 0x2823a83f0>
 signature: "v8@?0"
 invoke   : 0x100825da0 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
 copy     : 0x100825e04 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__copy_helper_block_e8_32r)
 dispose  : 0x100825e3c (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__destroy_helper_block_e8_32r)
```

这里的数据结构又发生了变化，多了 `copy` `dispose` 。

#### 5.3 栈block

![33](/files/-MkHRIeCB4kB2C4S3UPZ)

通过查看调用栈，我们发现并没有调用 `objc_retainBlock` 。没有进行 `_Block_copy` 操作。

### 六、 Block\_layout 结构分析

![35](/files/-MkHRIeDNAIYe2Z2bvWq)

#### 6.1 isa

block 的类型：全局、堆、栈

#### 6.2 flags

这些标志位都标明了不同的使用时间，有 `运行时：runtime` 有 `编译期：compiler`

```
// Values for Block_layout->flags to describe block objects
enum {
    /// 正在释放
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    /// 引用计数掩码
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    /// 需要释放
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    /// 是否拥有拷贝辅助函数 有的话就有 Block_descriptor_2
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    /// 是否拥有 block 析构函数
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    /// 标志是否有垃圾回收 MAC
    BLOCK_IS_GC =             (1 << 27), // runtime
    /// 是否是 全局block
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    /// 和 BLOCK_HAS_SIGNATURE 对应
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    /// 有签名 有的话就有 Block_descriptor_3
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    /// 有拓展 Layout
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};
```

#### 6.3 reserved

保留字段

#### 6.4 invoke

函数指针，即block中具体需要执行的内容。

#### 6.5 descriptor

这里我们在看下其中的 `Block_descriptor`

![36](/files/-MkHRIeEXJFm78mg1oSY)

这里就和我们之前输出的内容对应上了：

```
<__NSMallocBlock__: 0x2823a83f0>
 signature: "v8@?0"
 invoke   : 0x100825da0 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
 copy     : 0x100825e04 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__copy_helper_block_e8_32r)
 dispose  : 0x100825e3c (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__destroy_helper_block_e8_32r)
```

![37](/files/-MkHRIeFL-yLnSrUr0UU)

虽然 `Block_layout` 的内存结构中只有 `Block_descriptor_1` ，但是通过解读源码，可以发现， `Block_descriptor_2` 和 `Block_descriptor_3` 的读取是通过内存平移实现的，具体有哪些数据可读则是根据 `标识位` 来判别。

**Block\_descriptor\_1**

基础数据，一定有的

**Block\_descriptor\_2**

`COPY` 相关字段，有 `BLOCK_HAS_COPY_DISPOSE` 标志位时有。

**Block\_descriptor\_3**

签名相关数据，有 `BLOCK_HAS_SIGNATURE` 标志位时有。

### 七、 block 签名

上面的 `Block_descriptor_3` 中咱们提到了签名。如上面例子输出的 `signature: "v8@?0"` 很眼熟，它其实是 `Type Encodings 类型编码`，我们在 [属性](/wiki/ios/di-ceng/07.-shu-xing.md) 中有遇到过。

* `v8@?0`
  * `v` 表示返回值为空
  * `8` 表示参数的总大小
  * `@?` 表示block，
  * `0` 表示从0号字节开始

> 具体可以对照[官方文档](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)

**NSMethodSignature**

我们也可以通过 `NSMethodSignature` 来输出详细它的代表信息。

```
(lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"]
<NSMethodSignature: 0xac5982543c221e27>
    number of arguments = 1
    frame size = 224
    is special struct return? NO
    return value: -------- -------- -------- --------
        type encoding (v) 'v'
        flags {}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
        memory {offset = 0, size = 0}
    argument 0: -------- -------- -------- --------
        type encoding (@) '@?'
        flags {isObject, isBlock} // 是个对象，也是一个 block
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
        memory {offset = 0, size = 8}
```

### 八、 \_\_block 捕获变量的生命周期

![38](/files/-MkHRIeUos17cAl-lbk1)

我们再来研究下捕获变量的原理，我们继续通过汇编探索。

![39](/files/-MkHRIeV0DrN_VwZom9p)

这里会调用到 `_Block_object_assign`

我们找到相关源码：

#### 8.1 捕获变量类型

```
// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    /// 对象
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    /// block
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    /// __block 修饰的变量
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    /// 
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};
```

#### 8.2 \_Block\_object\_assign

```
//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
//
void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
        // 一、 捕获的是对象
      case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/

        /**
        _Block_retain_object 的实现：
        static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;

        _Block_retain_object_default 的实现：
        static void _Block_retain_object_default(const void *ptr __unused) { }

        其实什么都没做
        */
        _Block_retain_object(object);

        // 指针拷贝，引用计数 +1
        *dest = object;
        break;

        // 二、 捕获的是 block
      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/

        /**
        前文有关于 _Block_copy 的讲解么这里就不多说了
        */
        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
      // 三、 捕获的是 __block / __weak __block 修饰的变量
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/

        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      default:
        break;
    }
}
```

#### 8.3 \_Block\_byref\_copy

\_\_Block 捕获外界变量的操作 内存拷贝 等

```
static struct Block_byref *_Block_byref_copy(const void *arg) {
    // 使用 Block_byref 结构转换一下
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // 如果是通过引用计数管理的对象，就进行 malloc ，创建一个 copy
        // src points to stack
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;

        /**
        统一 forwarding 指向 copy
        所以 __block 修饰的指向的是同一片内存空间
        */ 
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy


        copy->size = src->size;

        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }

            /**
            捕获到了外部变量
            调用 byref_keep 
            内存处理
            被捕获变量生命周期的保存
            */
            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}
```

这里我们遇到了用来包装捕获变量的 `Block_byref` 结构体，还有个一个 `byref_keep` 与生命周期相关的函数调用，但是他们的来源并没有什么线索，我们只能继续从 `C++` 中寻找一些线索。

#### 8.4 Block\_byref

其中除了 `Block_byref` 还有 `Block_byref_2` ， `Block_byref_3` 。他们和前面的 `Block_descriptor_1` 一样，也是通过标志位区分，通过内存平移进行读写的

```
// __Block 修饰的结构体
struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

// __Block 修饰的结构体 byref_keep 和 byref_destroy 函数 - 来处理里面持有对象的保持和销毁
struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

// 拓展 layout
struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};
```

#### 8.5 通过 C++ 理解 Block\_byref 结构

位了更深入的理解 `Block_byref` 的结构，我们通过还原 `C++` 找一下线索：

```
__block NSNumber *num = @100;
void (^block)(void) = ^{
    num = @101;
    NSLog(@"%@", num);
};
block();
```

```
/**
对应的 Block_byref 类型结构体
*/
struct __Block_byref_num_0 {
  void *__isa;
__Block_byref_num_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSNumber *num;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_num_0 *num; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

...

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        // Block_byref 构造函数
        __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {
            /**
            Block_byref 的 
            void *isa;
            */
            (void*)0, 
            /**
            Block_byref 的 
            struct Block_byref *forwarding;
            */
            (__Block_byref_num_0 *)&num, 
            /**
            Block_byref 的 
            volatile int32_t flags;
            */ 
            33554432, 
            /**
            Block_byref 的 
            uint32_t size;
            */
            sizeof(__Block_byref_num_0), 
            /**
            Block_byref_2 的
            BlockByrefKeepFunction byref_keep;
            */ __Block_byref_id_object_copy
            __Block_byref_id_object_copy_131, 
            /**
            Block_byref_2 的
            BlockByrefDestroyFunction byref_destroy;
            */
            __Block_byref_id_object_dispose_131, 
            // *num
            ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 100)
            };
            
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    }
    return 0;
}
```

**byref\_keep**

再次调用 `_Block_object_assign` （详见 8.2），这时传入的 `flag` 为 `131` ，二进制为 `1000 0011`，而 `BLOCK_FIELD_IS_OBJECT = 3` ，所以会执行 `BLOCK_FIELD_IS_OBJECT` 相关的分支，进行 `拷贝` 与 `引用计数 + 1` 。

```
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
```

**byref\_destroy**

调用 `_Block_object_dispose` 进行销毁

```
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
```

### 九、 补充：面试题一解析

![40](/files/kr5cbZVi4neERb2or7fD)

#### 这里是 1 应该都没什么疑问

```objc
NSObject *obj = [NSObject new];
NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
```

#### 这里为什么是 3 呢？

```objc
void (^blockA)(void) = ^{
    NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockA();
```

* 这是引用了一个局部变量
  * 内部创建了一个结构体对 `obj` 进行了 `引用` ，引用计数+1
* 这个 `block` 被 `强引用` ，它是一个 `堆block`
  * 从上面的源码学习中我们得知， `block` 的类型是运行时确定的
  * 而在这个过程中，进行了 `_Block_copy`，引用计数 +1
* 所以这里打印出了 3

#### 为什么是 4

```objc
void (^ __weak blockB)(void) = ^{
    NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockB();
```

* 这是引用了一个局部变量
  * 内部创建了一个结构体对 `obj` 进行了 `引用` ，引用计数+1
* 这个 `block` 被 `弱引用` ，它是一个 `栈block`，引用计数不变

#### 为什么是5

```objc
void (^blockC)(void) = [blockB copy];
blockC();
```

* 将 栈blockB 进行了 copy，引用计数 +1

#### 总结

`__block` 捕获变量进行了三重拷贝：

* `变量A` 被 `__block` 修饰，就会被 `blockB` 从 `栈` 上 `拷贝` 到 `堆` 上
  * `_Block_copy`
* `blockB` 捕获 `Block_byref C` 并 `拷贝`
  * `_Block_object_assign` - `_Block_byref_copy`
* `Block_byref C` 对捕获的 `变量A` 进行拷贝
  * `_Block_object_assign` - `byref_keep`

### 参考

* [libclosure](https://opensource.apple.com/source/libclosure/)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ryukiedev.gitbook.io/wiki/ios/di-ceng/32.block.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
