5-7.【OC】【Block】__block 变量在 Stack → Heap 的迁移过程中,发生了什么变化?

1 阅读2分钟

这是一个非常硬核的内存迁移过程。当 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,这个变量会一直留在栈上,绝不会被迁移。