基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

weak 属性在对象被释放后会自动置为 nil,其底层实现原理是什么?

知识点图片

weak 属性在对象被释放后自动置为 nil,是 Objective-C Runtime(运行时)的一项重要特性。其底层实现原理可以概括为:Runtime 维护了一张全局的 Hash 表(SideTables),用于存储对象地址与指向该对象的所有 weak 指针地址之间的映射关系。

当对象被销毁时,Runtime 会通过这张表找到所有指向该对象的 weak 指针,并将它们指向的内存地址清零(置为 nil)。

下面是详细的底层实现剖析:

1. 核心数据结构

理解 weak 原理的关键在于理解 Runtime 内部的几个核心结构体:

  1. SideTables

    • 这是一个全局的静态数组(哈希数组)。
    • 为了防止多线程竞争导致锁冲突过于频繁,Runtime 使用了“分段锁”技术。SideTables 里面包含了多个 SideTable 结构(通常是 8 个或 64 个,取决于系统架构),通过对象的地址哈希运算后分配到不同的 SideTable 中。
  2. SideTable

    • 包含一个自旋锁(spinlock_t),用于保证线程安全。
    • 包含一个引用计数表(RefcountMap)。
    • 核心: 包含一个 weak_table_t(弱引用表)。
  3. weak_table_t

    • 这是一个全局弱引用哈希表。
    • Key:对象的内存地址(即 alloc 出来的那个对象的地址)。
    • Valueweak_entry_t 结构体。
  4. weak_entry_t

    • 这是存储具体 weak 指针的地方。
    • 它维护了一个数组(动态数组或定长数组),里面存储了所有指向该对象的 weak 指针变量的内存地址(即 &weakPointer)。

2. 实现流程

整个过程主要涉及两个阶段:设置 weak 变量对象销毁

阶段一:设置 weak 变量 (objc_storeWeak)

当你写下 __weak id obj = target; 时,编译器会将其转换为 Runtime 调用,最终调用 objc_storeWeak 函数。

  1. objc_initWeak / objc_storeWeak
    • 函数接收两个参数:weak 指针的地址(location)和被指向的对象(newObj)。
  2. 清理旧值
    • 如果该 weak 指针之前指向过其他对象(oldObj),Runtime 会以 oldObj 为 Key,在 weak_table 中找到对应的 weak_entry_t,并将该 weak 指针的地址从数组中移除。
  3. 注册新值
    • 如果 newObj 不为 nil,Runtime 会以 newObj 为 Key,在 weak_table 中查找。
    • 如果找不到,说明是第一个指向该对象的 weak 指针,则新建一个 weak_entry_t
    • 将 weak 指针的地址(location)添加到 weak_entry_t 的数组中。

阶段二:对象销毁与置 nil (dealloc)

这是 weak 自动置 nil 的核心步骤。当对象的引用计数变为 0 时,会触发 dealloc

调用链如下:
dealloc -> _objc_rootDealloc -> rootDealloc -> object_dispose -> objc_destructInstance -> objc_clear_deallocating

objc_clear_deallocating 的执行逻辑:

  1. 获取 SideTable
    • 根据当前要释放的对象地址,通过 Hash 算法找到它所属的 SideTable
  2. 查找 Weak 表
    • SideTableweak_table 中,根据对象地址找到对应的 weak_entry_t
  3. 遍历并置 nil
    • weak_entry_t 中保存了所有指向该对象的 weak 指针的地址。
    • Runtime 遍历这个数组,取出每个 weak 指针的地址。
    • 执行操作:*weak_ptr = nil;。这一步直接修改了内存中 weak 变量的值,使其指向 nil。
  4. 清理记录
    • 将该对象对应的 weak_entry_tweak_table 中移除。
    • SideTable 中移除该对象的引用计数记录。

3. 总结

一句话总结:
Runtime 维护了一个全局的 Hash 表,Key 是对象地址,Value 是指向该对象的所有 weak 指针地址的数组。当对象释放时,Runtime 根据对象地址查表,找到所有 weak 指针,将它们统一赋值为 nil,并从表中删除记录。

形象比喻:

  • 对象是“被关注的主播”。
  • Weak 指针是“粉丝”。
  • SideTables/weak_table 是“粉丝名册”。
  • 当你在代码中写 weak 时,就是把你的名字登记到了主播的粉丝名册上。
  • 当主播“退网”(dealloc)时,平台(Runtime)会拿出名册,挨个通知(赋值 nil)所有粉丝:“主播没了,别看了”。
00:00
00:00