闭包中的 [weak self] 和 [unowned self] 有什么区别?何时使用 unowned 是安全的?
在 Swift 的闭包(Closure)中使用 [weak self] 和 [unowned self] 都是为了解决 循环引用(Retain Cycle) 的问题,但它们在处理对象生命周期的方式上有本质的区别。
以下是详细的对比和使用建议:
1. 核心区别
| 特性 | [weak self] (弱引用) |
[unowned self] (无主引用) |
|---|---|---|
| 类型 | 可选类型 (Optional),即 Self? |
非可选类型 (Non-Optional),即 Self |
| 引用计数 | 不增加引用计数 | 不增加引用计数 |
| 对象释放后的行为 | 如果 self 被释放,引用自动变为 nil |
如果 self 被释放,引用不会变为空,访问它会导致 崩溃 (Crash) |
| 代码处理 | 需要解包 (如 guard let self = self else { return }) |
可以直接使用 self,不需要解包 |
| 安全性 | 安全。即使对象没了,代码也不会崩 | 不安全。如果对象没了还去访问,程序会崩溃 |
2. 代码示例对比
使用 [weak self]
由于 self 变成了可选类型,你需要处理它可能为 nil 的情况。
plaintext
class NetworkManager {
func fetchData(completion: @escaping () -> Void) {
// 模拟网络请求
DispatchQueue.global().async { [weak self] in
// 1. 使用可选链
self?.doSomething()
// 2. 或者使用 guard let 解包 (推荐)
guard let self = self else {
print("对象已被释放,停止执行")
return
}
self.doSomething()
}
}
func doSomething() { print("Done") }
}
使用 [unowned self]
你向编译器保证:“当这个闭包执行的时候,self 一定还活着。”
plaintext
class BankAccount {
var balance: Double = 0.0
lazy var printBalance: () -> Void = { [unowned self] in
// 不需要解包,直接使用,因为我们确信 self 存在
print("当前余额: \(self.balance)")
}
}
3. 何时使用 unowned 是安全的?
使用 unowned 的黄金法则是:闭包的生命周期 ≤ self 的生命周期。
也就是说,闭包不可能在 self 被释放后还在运行。如果存在任何“闭包可能比对象活得久”的可能性,就绝对不能用 unowned。
✅ 安全场景 (可以使用 unowned)
Lazy 属性初始化:
当闭包作为一个属性定义在类中,且该闭包只在这个类的内部使用,不会被传递到外部去异步执行时。- 例子:上面的
BankAccount例子。printBalance闭包属于BankAccount实例,如果实例销毁了,闭包也就随之销毁了,不存在“实例没了闭包还在跑”的情况。
- 例子:上面的
确定的父子关系:
当闭包被一个子对象持有,而子对象的生命周期严格受控于父对象(self),且子对象在父对象销毁时也会立即销毁。- 例子:一个 View Controller 持有一个自定义 View,View 中有一个闭包回调。如果 View Controller 释放了,View 也会释放,闭包也就没了。
❌ 不安全场景 (必须使用 weak)
异步操作 (Asynchronous Operations):
这是最容易导致 Crash 的地方。网络请求、定时器、GCD 延迟调用等。- 原因:你发起一个网络请求,用户退出了当前界面(
self被释放)。几秒后网络请求回调执行,如果用unowned,此时访问self就会崩溃。
- 原因:你发起一个网络请求,用户退出了当前界面(
闭包被存储在其他地方:
如果闭包被传递给了单例、或者其他生命周期比self长的对象持有时。
4. 总结与最佳实践
虽然 unowned 少了一次解包的过程,性能开销微乎其微地小于 weak,但在现代 iOS 开发中,为了代码的健壮性,建议遵循以下原则:
- 默认使用
[weak self]:除非你有 100% 的把握闭包和self同生共死,否则用weak。多写一行guard let换来的是 App 不会因为意外的生命周期问题而闪退。 - 在异步回调中永远使用
[weak self]:网络请求、动画完成回调等。 - 仅在 Lazy 属性或紧密耦合的同步逻辑中使用
[unowned self]。
一句话口诀:
怕死用
weak,命硬(确定同生共死)用unowned。如果不确定,就用weak。
右滑查看面试常问