Swift 提供了兩種方法解決你在使用類的屬性而產生的強引用循環:弱引用( weak
)和無主引用( unowned
)。
弱引用( weak
)和無主引用( unowned
)能確保一個實例在循環引用中引用另一個實例,而 不用 保持強引用關係。這樣實例就可以相互引用且不會產生強引用循環。
當一個實例的生命週期比較引用它的實例短,也就是這個實例可能會先於引用它的實例釋放的時候,需要使用弱引用( weak
)。對與一棟公寓來說在它的生命週期中是完全可以沒有住戶的,所以在這種情況下。相反,當一個實例擁有和引用它的實例相同的生命週期或是比引用它的實例更長的生命週期的時候,需要使用無主引用( unowned
)。
一、弱引用 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
關鍵字表示這是無主引用。
無主引用用於一個屬性允許設置爲 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
三、解決閉包引起的強引用循環
定義閉包的時候同時定義 捕獲列表 ,並作爲閉包的一部分,通過這種方式可以解決閉包和實例之間的強引用循環。捕獲列表定義了在閉包內部捕獲一個或多個引用類型的規則。像解決兩個實例的強引用循環一樣,將每一個捕獲類型聲明爲弱引用(weak
)或是無主引用(unowned
),而不是聲明爲強引用。至於是使用弱引用(weak
)還是無主引用(unowned
)取決於你代碼中的不同部分之間的關係。
注意
Swift 強制要求
閉包內部使用self
的成員,必須要寫成self.someProperty
或self.someMethod()
(而不是僅僅寫成someProperty
或someMethod()
)。這提醒你可能會一不小心就捕獲了self
。
當閉包和它捕獲的實例始終互相持有對方的時候,將閉包的捕獲定義爲無主引用,那閉包和它捕獲的實例總會同時釋放。
相反的,將捕獲定義弱引用時,捕獲的引用也許會在將來的某一時刻變成
nil
。弱引用總是可選類型的,並且,當引用的實例釋放的時候,弱引用自動變成nil
。 這就需要你在閉包內部檢查它的值是否存在。