Swift 提供了两种方法解决你在使用类的属性而产生的强引用循环:弱引用( weak
)和无主引用( unowned
)。
弱引用( weak
)和无主引用( unowned
)能确保一个实例在循环引用中引用另一个实例,而 不用 保持强引用关系。这样实例就可以相互引用且不会产生强引用循环。
当一个实例的生命周期比较引用它的实例短,也就是这个实例可能会先于引用它的实例释放的时候,需要使用弱引用( weak
)。对与一栋公寓来说在它的生命周期中是完全可以没有住户的,所以在这种情况下。相反,当一个实例拥有和引用它的实例相同的生命周期或是比引用它的实例更长的生命周期的时候,需要使用无主引用( unowned
)。
一、weak与unowned的区别
unowned
设置以后即使它原来引用的内容已经被释放了,它仍然会保持对被已经释放了的对象的一个 无效的 引用,它不能是 Optional
值,也不会被指向 nil
。如果你尝试调用这个引用的方法或者访问成员属性的话,程序就会崩溃。而 weak
则友好一些,在引用的内容被释放后,标记为 weak
的成员将会自动地变成 nil
(因此被标记为 @weak
的变量一定需要是 Optional
值)。
在闭包里面为了
解决循环引
问题,使用了[unowned self]
。如果回调在self
已经被释放后再调用,会导致crash
掉。
二、弱引用 weak
- 弱引用创建出来的变量是可选的
- 弱引用设置为
nil
的时候不会触发属性观察者
class Student {
var name: String = "student"
}
class Teacher {
var name: String?
// 弱引用属性时可选的
weak var student: Student? {
willSet {
print("willSet student")
}
didSet {
print("didSet student")
}
}
}
var student: Student? = Student()
var t = Teacher()
t.student = student
print(t.student)
print("--设置会触发属性观察 end--")
student = nil
print(t.student)
print("--不会触发属性观察 end--")
// 但是如果直接调用 t.student = nil 会触发属性观察
t.student = nil
print("--直接调用会触发属性观察 end--")
------
willSet student
didSet student
Optional(__lldb_expr_73.Student)
--设置会触发属性观察 end--
nil
--不会触发属性观察 end--
willSet student
didSet student
--直接调用会触发属性观察 end--
弱引用 不会强持有引用的实例,并且不会阻止 ARC
销毁引用的实例。这可以避免强引用循环。属性或是变量声明的前面加上 weak
关键词来表示这是弱引用。
弱引用多用于通常的解决循环引用问题场景。
三、无主引用 unowned
- 无主引用和弱引用类似,不会
retain
当前实例对象的引用,非可选,当一个对象被unowned
标识之后,假定永远有值,非可选类型 - 非可选类型,访问时有
crash
风险
class Student {
var name: String = "student"
}
class Teacher {
var name: String?
// 注意这里改成了unowned
unowned var student: Student? {
willSet {
print("willSet student")
}
didSet {
print("didSet student")
}
}
}
var student: Student? = Student()
var t = Teacher()
t.student = student
print(t.student as Any)
print("--设置会触发属性观察 end--")
student = nil
print(t.student) // 会在这里崩溃,因为student已经为nil
print("--不会触发属性观察 end--")
// 但是如果直接调用 t.student = nil 会触发属性观察
t.student = nil
print("--直接调用会触发属性观察 end--")
unowned crash.png
像弱引用一样,无主引用 也不会对指向的对象持有强引用。但是,与弱引用不同的是,无主引用适用于其他实例有相同的生命周期或是更长的生命周期的场景。属性或是变量声明前面加上 unowned
关键字表示这是无主引用。
无主引用用于一个属性允许设置为 nil
,而另一个属性不允许设置为 nil
,并会造成潜在的强引用循环的场景。
比如下面:
class Student {
var name: String = "student"
unowned let teacher: Teacher
init(name: String, t: Teacher) {
self.name = name
self.teacher = t
}
deinit {
print("student deinit")
}
}
class Teacher {
var name: String?
var student: Student?
init(name: String) {
self.name = name
}
deinit {
print("teacher deinit")
}
}
var t: Teacher? = Teacher(name: "teacherA")
t?.student = Student(name: "bill", t: t!)
t = nil
// Console out
teacher deinit
student deinit
unowned 初始化.png
释放t.png
三、解决闭包引起的强引用循环
定义闭包的时候同时定义 捕获列表 ,并作为闭包的一部分,通过这种方式可以解决闭包和实例之间的强引用循环。捕获列表定义了在闭包内部捕获一个或多个引用类型的规则。像解决两个实例的强引用循环一样,将每一个捕获类型声明为弱引用(weak
)或是无主引用(unowned
),而不是声明为强引用。至于是使用弱引用(weak
)还是无主引用(unowned
)取决于你代码中的不同部分之间的关系。
注意
Swift 强制要求
闭包内部使用self
的成员,必须要写成self.someProperty
或self.someMethod()
(而不是仅仅写成someProperty
或someMethod()
)。这提醒你可能会一不小心就捕获了self
。
-
当闭包和它捕获的实例始终互相持有对方的时候,将闭包的捕获定义为无主引用,那闭包和它捕获的实例总会同时释放。
-
相反的,将捕获定义弱引用时,捕获的引用也许会在将来的某一时刻变成
nil
。弱引用总是可选类型的,并且,当引用的实例释放的时候,弱引用自动变成nil
。 这就需要你在闭包内部检查它的值是否存在。
网友评论