在 ARC 环境下,这一行看似简单的赋值,背后其实是一套复杂的弱引用表(Weak Table)管理逻辑。编译器并不会把它当作普通的指针赋值,而是会将其转化为对 Runtime 函数的调用。
具体来说,编译器会将这行代码改写为类似以下的逻辑:
1. 核心 Runtime 函数的调用
编译器会将 __weak typeof(self) weakSelf = self; 转化为对 objc_initWeak 的调用:
C++
// 编译器实际生成的伪代码
id weakSelf;
objc_initWeak(&weakSelf, self);
objc_initWeak 内部发生了什么?
这个函数并不是简单的赋值,它的执行流程如下:
-
检测对象: 检查
self是否为nil。如果是nil,直接将weakSelf置为nil。 -
注册弱引用: 调用更底层的
objc_storeWeak。 -
进入弱引用表: 系统维护着一张全局的 SideTable(散列表),其中包含一张 Weak Table。
- 它以
self的内存地址作为 Key。 - Value 是一个数组,记录了所有指向该对象的
__weak指针地址(在这里,&weakSelf会被加入到这个数组中)。
- 它以
2. 为什么这样能防止 Retain?
这正是 __weak 的语义核心。在 objc_initWeak 的过程中:
- 没有 Retain: 整个注册过程仅仅是把
weakSelf的地址登记在案。它不会触发[self retain],因此self的引用计数保持不变。 - 安全监听:
self对象现在知道:“有一个叫weakSelf的指针在看着我”。
3. “自动置空” (Zeroing) 的伏笔
这一行代码还为 self 的销毁埋下了伏笔。当 self 的引用计数归零并执行 dealloc 时,Runtime 会执行以下收尾工作:
- 根据
self的地址去 Weak Table 中查找。 - 找到所有登记过的
__weak指针(包括我们的weakSelf)。 - 循环遍历数组,将这些指针全部赋值为
nil。 - 将该条目从 Weak Table 中移除。
4. 与 Block 的联动
当你随后在 Block 中捕获这个 weakSelf 时:
self.block = ^{ [weakSelf doSomething]; };
- 捕获语义: Block 的结构体内部会生成一个
__weak修饰的成员变量。 - 拷贝过程: 当 Block 从栈拷贝到堆时,系统会调用
objc_copyWeak,将外部weakSelf的弱引用状态同步给 Block 内部的那个变量,同样不会触发 retain。
总结对照
| 动作 | __strong self | __weak weakSelf |
|---|---|---|
| 编译器插入 | objc_retain | objc_initWeak |
| 内存效果 | 引用计数 +1 | 注册到全局 Weak Table |
| 销毁表现 | Block 不释放,self 就不死 | self 销毁时,指针自动变 nil |
| 底层性能 | 极快(仅原子自增) | 稍慢(需要查表、加锁) |