在 Swift 中,[weak self] 和 [unowned self] 都是为了打破闭包产生的循环引用,但它们在 Runtime(运行时)安全性和引用计数底层实现上有着本质的区别。
简单来说:weak 是“温和且安全”的,而 unowned 是“激进且高性能”的。
1. 引用计数类型的底层差异
Swift 的对象内部维护了三种引用计数(Reference Counts):Strong、Unowned 和 Weak。
[weak self]:弱引用
- 底层机制:
weak引用不直接指向对象,而是指向一个中间结构体 —— Side Table(侧表) 。 - 安全性:当对象的强引用计数归零时,对象会被销毁。由于有 Side Table 的存在,系统能够安全地将所有
weak指针置为nil。 - 表现:
self在闭包内部是 Optional 类型。你需要通过self?.doSomething()或guard let self = self来使用它。
[unowned self]:无主引用
- 底层机制:
unowned直接指向对象的内存地址,它会增加对象的 Unowned 引用计数。 - 安全性:当强引用计数归零时,对象进入“僵尸”状态(已析构但内存未回收),直到 Unowned 计数也归零,内存才被彻底释放。如果对象已销毁你仍尝试访问,程序会直接 Crash(类似于断言失败)。
- 表现:
self在闭包内部是非可选(Non-optional)的,可以直接像普通变量一样使用。
2. 生命周期与性能对比
| 特性 | [weak self] | [unowned self] |
|---|---|---|
| 可选性 | 是 (Optional) | 否 (Non-optional) |
| 性能 | 略低(需处理 Side Table 和可选解包) | 更高(直接访问内存,开销极小) |
| 安全性 | 绝对安全(自动置 nil) | 有风险(访问已释放对象会 Crash) |
| 内存释放 | 对象销毁后内存立即释放(Side Table 留存) | 必须等待所有 unowned 引用断开后内存才释放 |
3. 该选哪一个?
使用 weak 的场景(90% 的情况)
当闭包的生命周期可能比 self 更长时。例如:
- 网络请求回调。
- 延迟执行(
DispatchQueue.main.asyncAfter)。 - 任何你不确定
self是否还会活着的时候。
使用 unowned 的场景
只有当你确定闭包和 self 的生命周期是绑定的,或者闭包绝不会在 self 销毁后执行时。例如:
- 在
ViewController内部定义的私有闭包,且该闭包只在视图显示期间同步触发。 - 相互引用的两个对象具有相同的生命周期(同生共死)。
4. 底层的“僵尸”状态
这是一个有趣的细节:当一个对象被 unowned 引用时,如果强引用计数归零,Swift 会调用 deinit。此时对象虽然不再可用,但它的内存空间并不会立即归还给系统,而是保留一块“躯壳”用来存放引用计数信息,直到最后一个 unowned 指针消失。
总结
weak像是一个带安全锁的探测器,发现目标消失了就返回nil。unowned像是一根坚固的连杆,它假设目标永远在那,如果目标消失了你还去拉它,连杆就会折断(Crash)。