__weak 和 __unsafe_unretained 的区别?
__weak 和 __unsafe_unretained 都是 iOS 开发中 ARC(自动引用计数)环境下用于修饰弱引用的关键字。它们共同点是:都不会增加所指向对象的引用计数。
但是,它们在对象被销毁(dealloc)后的行为上有本质的区别。
以下是详细对比:
1. 核心区别:对象销毁后的行为
__weak(自动置 nil)- 当所指向的对象被释放(dealloc)时,ARC 运行时会自动将
__weak修饰的指针置为nil。 - 结果:向
nil发送消息是安全的(什么都不会发生),不会导致崩溃。 - 形象比喻:你牵着一条狗(对象),绳子松了(weak)。狗跑了(销毁),你手里的绳子会自动消失,你手里什么都没有了。
- 当所指向的对象被释放(dealloc)时,ARC 运行时会自动将
__unsafe_unretained(悬垂指针/野指针)- 当所指向的对象被释放时,
__unsafe_unretained修饰的指针保持原状,依然指向那个已经被回收的内存地址。 - 结果:这个指针变成了“悬垂指针”或“野指针”。如果你再次尝试访问这个指针,程序会崩溃(通常报
EXC_BAD_ACCESS)。 - 形象比喻:你牵着一条狗,狗跑了(销毁),但你手里还死死拽着那根绳子,指着那个空荡荡的地方。如果你试着去摸那只已经不存在的狗,你会被咬(崩溃)。
- 当所指向的对象被释放时,
2. 底层实现与性能
__weak- 实现原理:Runtime 维护了一张全局的 SideTables(哈希表)。当一个对象被
__weak指针引用时,Runtime 会将这个指针的地址注册到表中。当对象销毁时,Runtime 会去查表,找到所有指向该对象的__weak指针,并将它们全部置为nil。 - 性能:由于涉及到哈希表的查表、插入、删除和加锁操作,性能开销相对较大。
- 实现原理:Runtime 维护了一张全局的 SideTables(哈希表)。当一个对象被
__unsafe_unretained- 实现原理:仅仅是一个指针赋值操作,Runtime 不做任何额外的跟踪。
- 性能:性能非常高,几乎等同于 C 语言的原始指针赋值。
3. 使用场景
__weak- 首选方案。
- 用于解决循环引用(Retain Cycle),例如在 Block 中引用
self,或者 Delegate 模式。 - 适用于 UI 控件的
IBOutlet(虽然现在强引用也可以,但习惯上常用 weak)。
__unsafe_unretained- 极少使用,除非有极端的性能优化需求。
- 用于对象生命周期非常明确,且你能 100% 保证指针的生命周期短于对象的生命周期(即访问指针时对象一定还在)。
- 在 iOS 4 时代(ARC 刚出但不支持 weak)作为兼容方案使用(现在已无此需求)。
- 某些特定的 C 结构体或 Core Foundation 类型转换时可能用到。
4. 代码示例对比
使用 __weak:
plaintext
id obj = [[NSObject alloc] init];
__weak id weakPtr = obj;
NSLog(@"1. weakPtr: %@", weakPtr); // 输出对象地址
// 释放对象
obj = nil;
// 再次访问
NSLog(@"2. weakPtr: %@", weakPtr); // 输出 (null),安全
使用 __unsafe_unretained:
plaintext
id obj = [[NSObject alloc] init];
__unsafe_unretained id unsafePtr = obj;
NSLog(@"1. unsafePtr: %@", unsafePtr); // 输出对象地址
// 释放对象
obj = nil;
// 再次访问
// NSLog(@"2. unsafePtr: %@", unsafePtr); // 💥 必崩!访问了非法内存 (EXC_BAD_ACCESS)
总结
| 特性 | __weak |
__unsafe_unretained |
|---|---|---|
| 对象销毁后 | 指针自动变为 nil |
指针保持原地址(野指针) |
| 安全性 | 高(安全) | 低(不安全,易崩溃) |
| 性能 | 较低(依赖 Runtime 维护 SideTable) | 极高(无额外开销) |
| 对应属性修饰符 | @property (weak) |
@property (assign) (用于对象时) |
| 推荐程度 | 强烈推荐 | 尽量避免 |