循環(huán)強(qiáng)引用還會發(fā)生在當(dāng)你將一個閉包賦值給類實(shí)例的某個屬性谣沸,并且這個閉包體中又使用了這個類實(shí)例時锄弱。這個閉包體中可能訪問了實(shí)例的某個屬性聪舒,例如self.someProperty
知残,或者閉包中調(diào)用了實(shí)例的某個方法廓俭,例如self.someMethod()
云石。這兩種情況都導(dǎo)致了閉包“捕獲”self
,從而產(chǎn)生了循環(huán)強(qiáng)引用研乒。
解決閉包引起的循環(huán)強(qiáng)引用
在定義閉包時同時定義捕獲列表作為閉包的一部分汹忠,通過這種方式可以解決閉包和類實(shí)例之間的循環(huán)強(qiáng)引用。捕獲列表定義了閉包體內(nèi)捕獲一個或者多個引用類型的規(guī)則。跟解決兩個類實(shí)例間的循環(huán)強(qiáng)引用一樣宽菜,聲明每個捕獲的引用為弱引用或無主引用谣膳,而不是強(qiáng)引用。應(yīng)當(dāng)根據(jù)代碼關(guān)系來決定使用弱引用還是無主引用铅乡。
注意
Swift 有如下要求:只要在閉包內(nèi)使用self
的成員继谚,就要用self.someProperty
或者self.someMethod()
(而不只是someProperty
或someMethod()
)。這提醒你可能會一不小心就捕獲了self
阵幸。
定義捕獲列表
捕獲列表中的每一項(xiàng)都由一對元素組成花履,一個元素是weak
或unowned
關(guān)鍵字,另一個元素是類實(shí)例的引用(例如self
)或初始化過的變量(如delegate = self.delegate!
)挚赊。這些項(xiàng)在方括號中用逗號分開诡壁。
如果閉包有參數(shù)列表和返回類型,把捕獲列表放在它們前面:
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// 這里是閉包的函數(shù)體
}
如果閉包沒有指明參數(shù)列表或者返回類型咬腕,即它們會通過上下文推斷欢峰,那么可以把捕獲列表和關(guān)鍵字in
放在閉包最開始的地方:
lazy var someClosure: Void -> String = {
[unowned self, weak delegate = self.delegate!] in
// 這里是閉包的函數(shù)體
}
弱引用和無主引用
在閉包和捕獲的實(shí)例總是互相引用并且總是同時銷毀時葬荷,將閉包內(nèi)的捕獲定義為無主引用
涨共。
相反的,在被捕獲的引用可能會變?yōu)?code>nil時宠漩,將閉包內(nèi)的捕獲定義為弱引用
举反。弱引用總是可選類型,并且當(dāng)引用的實(shí)例被銷毀后扒吁,弱引用的值會自動置為nil
火鼻。這使我們可以在閉包體內(nèi)檢查它們是否存在。
注意
如果被捕獲的引用絕對不會變?yōu)閚il雕崩,應(yīng)該用無主引用魁索,而不是弱引用。
案例
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: Void -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
捕獲列表是[unowned self]
盼铁,表示“將self
捕獲為無主引用而不是強(qiáng)引用”粗蔚。
創(chuàng)建并打印HTMLElement
實(shí)例:
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// 打印 “<p>hello, world</p>”
使用捕獲列表后引用關(guān)系如下圖所示:
這一次,閉包以無主引用的形式捕獲
self
饶火,并不會持有HTMLElement
實(shí)例的強(qiáng)引用鹏控。如果將paragraph
賦值為nil
,HTMLElement
實(shí)例將會被銷毀肤寝,并能看到它的析構(gòu)函數(shù)打印出的消息:
paragraph = nil
// 打印 “p is being deinitialized”