5-11.【OC】【Block】self 是在什么时候被 retain 的?self.block = ^{ [self doSomething]; };

4 阅读2分钟

在 ARC 环境下,self 被 retain 的时机并不是在 Block 执行的时候,而是在 Block 创建并拷贝到堆上的那一刻。

我们可以把这个过程拆解为以下几个精确的步骤:


1. 核心触发时机:Block 拷贝到堆(Copying)

在你的代码中: self.block = ^{ [self doSomething]; };

  1. Block 创建: 编译器首先在栈上创建一个临时 Block。
  2. 捕获变量: 编译器发现 Block 内部使用了 self。此时,Block 的底层结构体会生成一个 __strong 类型的成员变量,并将当前的 self 地址存入。
  3. 赋值与拷贝: 因为 self.block 通常是一个 strong 属性,当你进行赋值操作时,会触发 objc_retainBlock
  4. 真正 Retain 的时刻:objc_retainBlock 内部,系统会将栈上的 Block 拷贝到堆(Malloc)。就在这一瞬间,系统会调用底层函数(如 _Block_object_assign),它会探测到捕获列表里有一个 __strong 修饰的 self,于是对 self 发起一次 retain 操作。

2. 为什么不是在执行时才 retain?

如果你认为是在 [self doSomething] 执行时才 retain,那就太晚了。

Block 的设计初衷是**“环境捕获” 。为了保证 Block 在未来某个时刻执行时,它所依赖的变量(self)依然活着,它必须在出生(进入堆)**的那一刻就牢牢抓珠这个对象,增加它的引用计数。


3. 引用计数的变化过程

假设 self 最初的引用计数为 1

  1. 赋值前: self 引用计数 = 1。

  2. 赋值过程中 (self.block = ...):

    • Block 被创建并拷贝到堆。
    • Block 内部的强引用成员变量持有 self
    • self 引用计数变为 2(Block 成了 self 的第二个“主人”)。
  3. 如果 block 属性是 strong:

    • self 又持有这个 Block。
    • 环形成: self 引用计数为 2,Block 引用计数为 1。

4. 特殊情况:如果 Block 没被拷贝到堆?

如果这个 Block 只是一个临时变量,且没有赋值给任何强引用变量(即它留在栈上):

  • ARC 下,虽然它在栈上,但它依然会持有 self
  • 但由于栈上的 Block 生命周期极短(函数结束即销毁),它会立刻对 self 执行一次 release。这种情况下你几乎观察不到引用计数的变化,也不会产生循环引用。

总结

self 被 retain 的真实时间点: 执行赋值语句 self.block = ... 的那一行代码时,Block 被拷贝到堆内存的过程之中。