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

### 一、 block 的分类

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

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

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

**未捕获任何变量**

![1](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fa177762761cda1c6cb66582016cd31858455020c.png?generation=1632396986144546\&alt=media)

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

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

![4](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fc324aa43a460138691d5d7d075d53405911988b6.png?generation=1632396987899702\&alt=media)

#### 1.2 堆 block

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

![2](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fecc1de7b8f12f6007529e4c554204fbdb0b9726a.png?generation=1632396989136342\&alt=media)

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

#### 1.3 栈 block

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

![3](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fc1ed7429771b3e17639716ba8c647ea640ad3681.png?generation=1632396991818768\&alt=media)

```
<__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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F6eb5f8c3b5161ffcb7b76ff666381624e2290ab1.png?generation=1632396986159633\&alt=media)

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

![6](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Ff6d21ed98db4847b8f64d6f390db9b80ed4f77fe.png?generation=1632396986159611\&alt=media)

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

#### 2.2 面试题三解读

![7](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F2a26188f06417e2e62bb2e58eb7717e58767fb70.png?generation=1632396992596844\&alt=media)

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

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

![8](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F01d2ebe2c26d2b5932f8ca22f6451c3c728d41c9.png?generation=1632396986197825\&alt=media)

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

![9](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F89a1024a90e14580b077af74a6deab12c026cecb.png?generation=1632396987921409\&alt=media)

赋值给 `blockA` 。

![10](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F6372072b051ab7646bb5f661eb128022007ea091.png?generation=1632396992147840\&alt=media)

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

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

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

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

![11](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F4d84d7a492716704554b9e001fdf8089a0365c2b.png?generation=1632396994152454\&alt=media)

`blockB` 为 `栈block` 。

![12](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F8eaea232c1884d51dca9750f43dada8b6f520b58.png?generation=1632396990588185\&alt=media)

赋值给 `blockA` 。

![13](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fbef5813409a9241f10b017478899e9d8af6b2022.png?generation=1632396990849343\&alt=media)

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

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

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

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

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

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

![14](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F67b1a1c790c719ae77b795a1028f205c37e7b541.png?generation=1632396985640149\&alt=media)

```
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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F4cf774ed0e6193fbddf600b86a4e80aacd1d0a98.png?generation=1632396987814052\&alt=media)

我们发现多了几种数据类型： `__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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fc62f6c34fece5b642de66005df73ecaaf1e558bc.png?generation=1632396996939058\&alt=media)

#### 3.4 全局 block

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

![17](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Feb970882b3a4f8f5abd242103801cc002bc4126c.png?generation=1632396988039614\&alt=media)

#### 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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F26857f90e3e037dabadb117453d05f64fb1ce221.png?generation=1632396990167792\&alt=media)

![19](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fef538a5188a630709a16c35bb1047671652c9d2d.png?generation=1632396988264622\&alt=media)

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

#### 3.6 \_NSConcreteStackBlock

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

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

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

![20](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F198c5552a5f831dd6a1b1bd9aba235f4c94b7dc7.png?generation=1632396989957186\&alt=media)

#### 4.1 进入汇编断点

![21](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fb63c54d47c3aed8f955b864fe1d896a6f0c90c9b.png?generation=1632396989256982\&alt=media)

#### 4.2 objc\_retainBlock

![22](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F2591c3c415883578e4c22c37262d8353cb40d580.png?generation=1632396988470183\&alt=media)

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

![23](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F6a50231dfc171b88559379d728f0570497686bea.png?generation=1632396986852175\&alt=media)

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

#### 4.3 \_Block\_copy

![24](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F2ce1f8730f791a2e8af1acbcd5c43f86851b6ccd.png?generation=1632396988895871\&alt=media)

这例进入了 `_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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F0e61aa7bdad2d7ac403bbdf311327a54cfa37d33.png?generation=1632396990986628\&alt=media)

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

![26](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F4420d144d6b2e5d3cffa77663d13af74e681f12a.png?generation=1632396986805210\&alt=media)

进入 `_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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F9f7c66b2d38a4c92ce8532ee7239ae016c0445e1.png?generation=1632396990363441\&alt=media)

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

![28](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fb5da63faabb1a8194aee257ae332e145d751f076.png?generation=1632396985450495\&alt=media)

```
<__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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F7a178406db445a946042e5561fba2c83601aa24e.png?generation=1632396989814099\&alt=media)

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

![30](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F35b082ca589aca87ad101fcf60aefd0db4068585.png?generation=1632396988761867\&alt=media)

这里已经变成了 堆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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F4c727f03533b1f6163e3b390deed5e92b400d65e.png?generation=1632396991472634\&alt=media)

```
<__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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F47fcb4d5eb3ea248172955b9775a494d95fa66bf.png?generation=1632396991287466\&alt=media)

```
<__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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fe64536e5a5378c8f96657b52b0358fe3bee3b95d.png?generation=1632396993269830\&alt=media)

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

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

![35](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F0f9611b5fd552637e13654dfe5d41105aca3e8dc.png?generation=1632396989491908\&alt=media)

#### 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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F0b700e84022f9876c8b48b4ecd6bb705092ca37b.png?generation=1632396986985377\&alt=media)

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

```
<__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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fa51fabd9f39e947c03f1e4cf445e62fcf446ef0b.png?generation=1632396992380787\&alt=media)

虽然 `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 类型编码`，我们在 [属性](https://ryukiedev.gitbook.io/wiki/ios/di-ceng/07.-shu-xing) 中有遇到过。

* `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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F6dbddcf85a7ffadf3f1a0e29bf531fe8bbdd0a98.png?generation=1632396989439312\&alt=media)

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

![39](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F4f97d9569406cddc3447e8462df481bf9999d6a7.png?generation=1632396987181457\&alt=media)

这里会调用到 `_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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-87fb350c281507ca56bb61e40b8214702573b6f3%2F32-40.png?alt=media)

#### 这里是 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/)
