5-12.【OC】【Block】__weak typeof(self) weakSelf = self; 这行代码在 ARC 下编译器实际插入了什么逻辑?

2 阅读2分钟

在 ARC 环境下,这一行看似简单的赋值,背后其实是一套复杂的弱引用表(Weak Table)管理逻辑。编译器并不会把它当作普通的指针赋值,而是会将其转化为对 Runtime 函数的调用。

具体来说,编译器会将这行代码改写为类似以下的逻辑:


1. 核心 Runtime 函数的调用

编译器会将 __weak typeof(self) weakSelf = self; 转化为对 objc_initWeak 的调用:

C++

// 编译器实际生成的伪代码
id weakSelf;
objc_initWeak(&weakSelf, self);

objc_initWeak 内部发生了什么?

这个函数并不是简单的赋值,它的执行流程如下:

  1. 检测对象: 检查 self 是否为 nil。如果是 nil,直接将 weakSelf 置为 nil

  2. 注册弱引用: 调用更底层的 objc_storeWeak

  3. 进入弱引用表: 系统维护着一张全局的 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 会执行以下收尾工作:

  1. 根据 self 的地址去 Weak Table 中查找。
  2. 找到所有登记过的 __weak 指针(包括我们的 weakSelf)。
  3. 循环遍历数组,将这些指针全部赋值为 nil
  4. 将该条目从 Weak Table 中移除。

4. 与 Block 的联动

当你随后在 Block 中捕获这个 weakSelf 时:

self.block = ^{ [weakSelf doSomething]; };

  • 捕获语义: Block 的结构体内部会生成一个 __weak 修饰的成员变量。
  • 拷贝过程: 当 Block 从栈拷贝到堆时,系统会调用 objc_copyWeak,将外部 weakSelf 的弱引用状态同步给 Block 内部的那个变量,同样不会触发 retain

总结对照

动作__strong self__weak weakSelf
编译器插入objc_retainobjc_initWeak
内存效果引用计数 +1注册到全局 Weak Table
销毁表现Block 不释放,self 就不死self 销毁时,指针自动变 nil
底层性能极快(仅原子自增)稍慢(需要查表、加锁)