在 ARC 环境下,self 被 retain 的时机并不是在 Block 执行的时候,而是在 Block 创建并拷贝到堆上的那一刻。
我们可以把这个过程拆解为以下几个精确的步骤:
1. 核心触发时机:Block 拷贝到堆(Copying)
在你的代码中: self.block = ^{ [self doSomething]; };
- Block 创建: 编译器首先在栈上创建一个临时 Block。
- 捕获变量: 编译器发现 Block 内部使用了
self。此时,Block 的底层结构体会生成一个__strong类型的成员变量,并将当前的self地址存入。 - 赋值与拷贝: 因为
self.block通常是一个strong属性,当你进行赋值操作时,会触发objc_retainBlock。 - 真正 Retain 的时刻: 在
objc_retainBlock内部,系统会将栈上的 Block 拷贝到堆(Malloc)。就在这一瞬间,系统会调用底层函数(如_Block_object_assign),它会探测到捕获列表里有一个__strong修饰的self,于是对self发起一次retain操作。
2. 为什么不是在执行时才 retain?
如果你认为是在 [self doSomething] 执行时才 retain,那就太晚了。
Block 的设计初衷是**“环境捕获” 。为了保证 Block 在未来某个时刻执行时,它所依赖的变量(self)依然活着,它必须在出生(进入堆)**的那一刻就牢牢抓珠这个对象,增加它的引用计数。
3. 引用计数的变化过程
假设 self 最初的引用计数为 1:
-
赋值前:
self引用计数 = 1。 -
赋值过程中 (
self.block = ...):- Block 被创建并拷贝到堆。
- Block 内部的强引用成员变量持有
self。 self引用计数变为 2(Block 成了self的第二个“主人”)。
-
如果 block 属性是 strong:
self又持有这个 Block。- 环形成:
self引用计数为 2,Block 引用计数为 1。
4. 特殊情况:如果 Block 没被拷贝到堆?
如果这个 Block 只是一个临时变量,且没有赋值给任何强引用变量(即它留在栈上):
- 在 ARC 下,虽然它在栈上,但它依然会持有
self。 - 但由于栈上的 Block 生命周期极短(函数结束即销毁),它会立刻对
self执行一次release。这种情况下你几乎观察不到引用计数的变化,也不会产生循环引用。
总结
self 被 retain 的真实时间点: 执行赋值语句 self.block = ... 的那一行代码时,Block 被拷贝到堆内存的过程之中。