2-9.【OC】【内存管理】weak 表是什么时候被清理的?

1 阅读3分钟

weak 表的清理发生在一个对象的 生命终点,确切地说,是在 dealloc 流程执行到 objc_destructInstance 这一步时。

清理过程并不是一蹴而就的,而是一场严密的“定点清除”行动。


1. 清理的触发时机:dealloc 流程

当对象的引用计数归零,系统调用 dealloc。为了保证效率,Runtime 并没有在第一时间清理 weak 表,而是先进行一系列状态检查。

清理的完整调用链如下:

  1. _objc_rootDealloc
  2. rootDealloc
  3. objc_destructInstance
  4. clearDeallocating (核心清理函数)

2. 核心函数:clearDeallocating

这是 weak 指针变 nil 的真正时刻。在 clearDeallocating 内部,Runtime 会做两件事:

  • 检查 isa 标志位:首先读取 isa 指针。如果 weakly_referenced 位为 0,说明没有任何人弱引用过这个对象,直接跳过清理,极大提升销毁速度。
  • 执行 weak_clear_no_lock:如果标志位为 1,则进入 SideTable 锁定 weak_table,开始地毯式搜索。

3. 清理的三个物理步骤

当进入 weak_clear_no_lock 后,清理工作分为以下三步:

  1. 定位 Entry:通过被销毁对象的 地址 作为 Key,在全局的 weak_table 中找到对应的 weak_entry_t(也就是存储该对象所有弱引用指针的数组)。
  2. 强制置空 (Zeroing) :遍历该 Entry 中的所有弱引用指针地址。执行类似 *ptr = nil 的操作。这就是为什么你的变量会在那一瞬间突然变成 nil
  3. 从表中抹除:一旦所有指针都已置空,该 Entry 会从 weak_table 哈希表中移除,腾出内存供其他对象使用。

4. 为什么不能提前清理?

你可能会想,为什么不在引用计数归零的那一刻就清理?

  • 竞态条件 (Race Condition) :在 dealloc 的早期阶段,对象可能还在执行自定义的销毁逻辑。如果提前清理了 weak 表,那么在该对象的 dealloc 方法里,如果尝试使用 weakSelf 就会拿到 nil,导致逻辑错误。
  • 性能考量weak 表的清理涉及全局 SideTable 的加锁。将其放在销毁流程的最后一步,可以确保对象已经处于“准死亡”状态,减少锁持有的时间。

5. 常见误区:对象死了,weak 指针就一定变 nil 吗?

绝大多数情况是,但不绝对。

有一种特殊情况:如果对象所在的内存被直接 free() 掉,而没有走 Objective-C 的 dealloc 流程(例如你在底层用 C 的方式操作内存),那么 weak 表里对应的记录永远不会被触发清理。这会导致 weak 指针变成一个野指针,再次访问就会 Crash。所以,永远不要绕过 Runtime 手动管理 Objective-C 对象的生命周期。


总结

weak 表是在对象 物理销毁的前一刻(即 dealloc 内部)被清理的。它的清理标志着对象作为“引用实体”在内存中的彻底消失。