简单来说,__block 修饰符的作用是**“将一个栈上的变量包装成一个对象”**,从而让它能够随着 Block 一起被拷贝到堆上,实现跨作用域的读写访问。
如果不加 __block,Block 捕获局部变量只是值传递(将其值拷贝到 Block 的结构体成员中);加上 __block 后,就变成了引用传递的底层实现。
1. 底层结构:从“基本类型”到“结构体对象”
当你给一个变量加上 __block 时,编译器会将该变量重构为一个结构体:Block_byref。
以 __block int age = 10; 为例,底层会转化为类似这样的结构:
C++
struct __Block_byref_age_0 {
void *__isa; // 对象指针
struct __Block_byref_age_0 *__forwarding; // 关键:指向自身的指针
int __flags;
int __size;
int age; // 实际存储的变量值
};
此时,你在代码中对 age 的读写,实际上都会变成 age->__forwarding->age。
2. 关键机制:__forwarding 指针
这是 __block 最精妙的设计,解决了栈和堆共存时的同步问题。
- 在栈上时:
__forwarding指向变量自身(栈上的结构体)。 - 拷贝到堆时: 当 Block 从栈拷贝到堆,
__block变量也会被拷贝到堆。此时,栈上的__forwarding指向堆上的副本,而堆上的__forwarding指向它自己。
为什么要这么做?
这样无论你在原来的函数里(访问栈变量)还是在异步 Block 里(访问堆变量)修改 age,最终操作的都是堆上的同一份数据。
3. 内存管理:Copy 与 Retain
当 Block 执行 copy 操作时,底层会调用 _Block_object_assign 函数。对于 __block 变量:
- 它会触发
__Block_byref结构体的拷贝。 - 增加该结构体的引用计数。
- 如果多个 Block 捕获了同一个
__block变量,它们会共同持有堆上的那一份,直到所有 Block 都销毁,变量才会被释放。
4. 总结:加与不加的区别
| 特性 | 普通捕获 (const copy) | __block 捕获 |
|---|---|---|
| 底层实现 | 直接拷贝变量的值 | 包装成 Block_byref 结构体 |
| 可修改性 | Block 内部不可修改(只读) | Block 内部可修改 |
| 内存位置 | 随 Block 拷贝而拷贝值 | 独立包装,支持 __forwarding 转发 |
| 解决场景 | 仅需获取快照 | 需要在 Block 内部改变外部变量 |
一个有趣的细节
在 MRC 下,__block 还可以用来避免循环引用(因为它不会 retain 捕获的对象);但在 ARC 下,__block 会对对象产生强引用,所以 ARC 下解决循环引用必须用 __weak。