2-8.【OC】【内存管理】weak 表(Side Table)里到底存了什么?

2 阅读2分钟

在 Objective-C 的底层实现中,SideTable 是管理内存安全的核心仓库。它并不是一张简单的表,而是一个复杂的 多级嵌套数据结构,专门用来存储引用计数和弱引用信息。

我们可以通过“三层嵌套”的逻辑来剥开它的本质:


第一层:SideTables (全局散列表)

为了避免在多线程环境下,全球的对象共用一把锁导致性能瓶颈(锁竞争),系统并没有只创建一个巨大的表,而是维护了一个 SideTables 数组

  • 结构:一个固定长度的哈希数组(在不同架构下长度不同,如 8 或 64)。
  • 分配逻辑:对象通过其内存地址进行哈希运算,映射到其中某一个 SideTable 上。
  • 作用:将锁的粒度拆分。操作对象 A 的引用计数时,只会锁住对象 A 所在的那个 SideTable,而不影响其他桶里的对象。

第二层:SideTable (具体的结构体)

当你定位到某一个 SideTable 时,你会发现它内部包含三个关键成员:

C++

struct SideTable {
    spinlock_t slock;           // 自旋锁,保证操作的线程安全
    RefcountMap refcnts;        // 引用计数表(哈希表)
    weak_table_t weak_table;    // 弱引用表(核心所在)
};
  • slock:确保在修改该表内的引用计数或弱引用关系时,其他线程无法同时写入。
  • refcnts:当 isa 指针里的 extra_rc 位溢出时,多出来的引用计数就会存到这个哈希表里。

第三层:weak_table_t (弱引用的核心)

这是你问题的核心。weak_table 里面存储的是 对象地址弱引用指针地址 的映射。

weak_table_t 内部,又包含了一个 weak_entries 数组。每一个 weak_entry_t 对应一个对象:

  1. Key (referent) :被引用对象的 内存地址

  2. Value (referring_pointers) :一个 数组,里面存的是所有指向该对象的 __weak 指针的地址

    • 注:当弱引用较少时(< 4个),它直接存放在结构体内部的静态数组里;当弱引用变多时,会动态开辟哈希数组存储。

总结:当一个对象销毁时发生了什么?

  1. 对象调用 dealloc
  2. Runtime 找到该对象对应的 SideTable
  3. weak_table 中,以该 对象地址 为 Key,找到对应的 weak_entry_t
  4. 取出该 Entry 里的 所有弱引用指针地址
  5. 遍历这些地址,将其内容清空:*ptr = nil
  6. 将该 Entry 从 weak_table 中移除。

💡 一个有趣的细节:为什么不用更简单的数据结构?

苹果之所以设计这么复杂的嵌套(SideTables -> SideTable -> weak_table -> weak_entry),是为了在 内存开销并发性能 之间取得极致平衡:

  • 分片锁:降低了多核 CPU 下的竞争。
  • 哈希查找:保证了即便有数百万个对象,寻找某个对象的弱引用表也是 O(1)O(1) 级别的复杂度。