2-20.【OC】【内存管理】Swift 的 [weak self] 与 [unowned self] 的底层区别是什么?

2 阅读3分钟

在 Swift 中,[weak self][unowned self] 都是为了打破闭包产生的循环引用,但它们在 Runtime(运行时)安全性引用计数底层实现上有着本质的区别。

简单来说:weak 是“温和且安全”的,而 unowned 是“激进且高性能”的。


1. 引用计数类型的底层差异

Swift 的对象内部维护了三种引用计数(Reference Counts):StrongUnownedWeak

[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)。