在 Swift 中,weak
和 unowned
都用于打破引用循环(Retain Cycle),但是它们的使用场景和行为有所不同,下面讲解一下他们的区别及用法。
一、weak
Object 本身可以独立存在,并且一定是 Optional。
当赋值给一个被标记 weak
的变量时,它的引用计数不会改变。且当这个弱引用变量被释放时,这个变量将被自动设为 nil,这也是 weak
必须被声明为 Optional 的原因。
闭包中的循环引用:
当闭包被对象强引用,且闭包内部又捕获了对象自身时,会导致循环引用。
class NetworkManager {
func handleData() {}
func fetchData() {
// 模拟异步网络请求
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
// 使用 weak 避免循环引用,self 可能为 nil
self?.handleData()
}
}
}
当 NetworkManager 在异步操作完成前被释放,self 会自动变为 nil,避免导致程序崩溃。
二、unowned
Object 本身和连结对象需要同时存在,可以是 Optional。
被 unowned
标记的变量,即使它原来的引用已经被释放,它仍然会保持对被释放的对象的一个“无效”引用。如果它不是 Optional,它也不会被指向 nil。所以此时如果试图去访问该变量,程序就会崩溃。
对象间的父子关系:
当一个对象的生命周期严格依赖另一个对象时,可以使用 unowned
。
class Customer {
var creditCard: CreditCard?
}
class CreditCard {
// 非 Optional,其生命周期与 Custmer 一致
unowned let owner: Customer
init(owner: Customer) {
self.owner = owner
}
}
var customer: Customer? = Customer()
customer!.creditCard = CreditCard(owner: customer!)
// Customer 和 CreditCard 同时释放
customer = nil
避免可选解包的麻烦:
当两个对象生命周期完全一致时,unowned
可以简化代码。
class Parent {
var child: Child
init() {
child = Child(parent: self)
}
}
class Child {
// 非 Optional,因为 Parent 一定存在
unowned let parent: Parent
init(parent: Parent) {
self.parent = parent
}
}
var parent: Parent? = Parent()
// Parent 和 Child 同时释放
parent = nil
就算 unowned 被设为了 Optional,在存取 unowned 的时候,如果 Optional 本身已经被转换成了 case some,那它还是会继续进行存取,所以仍然可能导致程序崩溃。
三、如何选择
根据苹果的官方文档的建议,当我们知道两个对象的生命周期并不相关,那我们必须使用 weak
。相反,非强引用对象拥有和强引用对象同样或者更长的生命周期的话,则应使用 unowned
。
- 使用
weak
的场景:- 闭包可能被异步执行,且对象可能提前释放(如网络请求、定时器等)。
- 引用对象可能为 nil。
- 使用
unowned
的场景:- 两个对象的生命周期严格绑定(如父子关系)。
- 需要避免可选类型(Optional)解包,且确保引用对象不会被提前释放。
场景 | weak | unowned |
---|---|---|
闭包中的异步操作 | ✅安全 | ❌可能导致程序崩溃 |
父子对象关系 | ❌不必要 | ✅简洁且安全 |
对象可能为 nil | ✅安全 | ❌不适用 |