从底层实现来看,__unsafe_unretained 和 __weak 的差异本质上是 “原始指针” 与 “运行时托管指针” 的区别。
虽然它们都不增加引用计数,但处理对象销毁(Deallocation)的方式完全不同。
1. 底层实现机制的差异
__weak:受 Runtime 托管的“智能指针”
当你声明一个 __weak 变量时,Runtime 会介入并维护一套复杂的账本。
- 注册机制:调用
objc_initWeak。系统会将该指针的地址注册到全局的SideTable(哈希表)中。 - 自动置空(Zeroing) :当指向的对象销毁时,Runtime 会回溯
SideTable,找到所有指向该对象的__weak指针,并将它们物理清零(设为nil)。 - 读取保护:每次读取
__weak变量时,底层会调用objc_loadWeakRetained,临时retain对象以确保在读取过程中对象不会突然消失。
__unsafe_unretained:纯粹的 C 语言指针
__unsafe_unretained 几乎不涉及任何 Runtime 逻辑。
- 无注册:它仅仅是一个地址记录。编译器不会在
SideTable中留下任何记录。 - 无清理:当对象销毁时,没有任何机制去通知或修改这个指针。它依然保存着那个已经失效的内存地址。
- 无保护:读取时直接访问地址,没有任何安全检查。
2. 性能与安全性的博弈
| 特性 | __weak | __unsafe_unretained |
|---|---|---|
| 安全性 | 高。自动变 nil,防止野指针崩溃。 | 低。对象销毁后变成悬挂指针(Dangling Pointer)。 |
| 性能开销 | 较高。涉及哈希查找、自旋锁、弱引用表维护。 | 极低。等同于普通 C 指针操作,无额外开销。 |
| 适用范围 | 绝大多数循环引用处理场景。 | 极其追求性能的底层算法、或已知生命周期必长于自身的场景。 |
3. 崩溃的本质原因
这就是为什么它叫 “unsafe” :
-
__weak场景:obj销毁 指针变nil调用[nil someMethod]安全(Obj-C 支持向 nil 发消息) 。 -
__unsafe_unretained场景:obj销毁 指针保留旧地址 访问旧地址 发现内存已被系统挪作他用或不可访问 EXC_BAD_ACCESS (Crash) 。
4. 为什么要保留 __unsafe_unretained?
既然 __weak 这么好,为什么不彻底废除 __unsafe_unretained?
- 性能极致优化:在每秒执行数百万次的小对象操作中,
__weak的锁竞争和哈希查找开销可能成为瓶颈。 - 兼容性:
__weak机制需要硬件和操作系统的支持(iOS 5+),在极早期的系统或特定的底层 C/C++ 混编环境下,__unsafe_unretained兼容性更强。 - 非对象类型:在某些复杂的内存映射或对非
isa结构的操作中,Runtime 无法介入。
总结
__weak是有**“售后服务”**的:它在后台偷偷帮你擦屁股,确保你不会因为对象消失而摔倒。__unsafe_unretained是**“后果自负”**的:它只管记录地址,至于那个地址现在是金矿还是地雷,它一概不理。