这是一个非常硬核的内存迁移过程。当 Block 从栈拷贝到堆时,__block 变量并不是简单地“移动”,而是一场坐标转换和关系重定位。
我们可以把这个过程拆解为三个关键动作:
1. 结构体的“克隆” (Memory Copy)
首先,系统会在**堆(Heap)**上开辟一块空间,大小与栈上的 __Block_byref 结构体完全一致。接着,将栈上结构体里的所有内容(包括你定义的变量值、isa 指针等)原封不动地拷贝到堆上。
2. __forwarding 指针的“重定向” (The Forwarding Magic)
这是最核心的一步,也是 __block 变量能保持同步的秘诀。
- 栈上的
__forwarding: 原本指向栈上的自己,现在改为指向堆上的新副本。 - 堆上的
__forwarding: 指向它自己。
为什么要这样改?
假设你的代码正在运行,由于指令流水线的原因,有些操作可能还在访问旧的栈地址。通过重定向 __forwarding,即使代码访问的是栈上的旧结构体,它也会顺着指针立刻“跳”到堆上,从而保证无论在哪操作,访问的都是堆上的同一份数据。
3. 引用计数的接管
- 在堆上创建副本后,该结构体的引用计数会被初始化为 1。
- 如果后续还有其他 Block 捕获这个
__block变量并被拷贝到堆,系统发现堆上已经有了副本,就不会再重复拷贝,而是简单地增加堆上副本的引用计数。 - 栈上的结构体此时变成了一个“空壳”,它只负责通过
__forwarding转发请求。
迁移前后的逻辑对比
| 状态 | 变量位置 | __forwarding 指向 | 修改变量的影响 |
|---|---|---|---|
| 拷贝前 | 仅在栈上 | 指向栈自己 | 仅栈内有效 |
| 拷贝后 (栈) | 栈(作为中转) | 指向堆副本 | 通过转发修改堆上的值 |
| 拷贝后 (堆) | 堆(真实实体) | 指向堆自己 | 修改堆上的值 |
最后的生命周期:销毁
当所有持有该 __block 变量的 Block 都被销毁(引用计数归零)时,堆上的 __Block_byref 结构体才会执行类似 dealloc 的清理函数(__Block_byref_destroy),并释放它所持有的对象。
这里有个冷知识:
在 MRC 环境下,如果你只是给变量加了 __block 但没有对 Block 执行 copy,这个变量会一直留在栈上,绝不会被迁移。