前面我們看到了循環(huán)強(qiáng)引用環(huán)是在兩個(gè)類實(shí)例屬性互相保持對(duì)方的強(qiáng)引用時(shí)產(chǎn)生的,還知道了如何用弱引用和無(wú)主引用來(lái)打破循環(huán)強(qiáng)引用。
循環(huán)強(qiáng)引用還會(huì)發(fā)生在當(dāng)你將一個(gè)閉包賦值給類實(shí)例的某個(gè)屬性,并且這個(gè)閉包體中又使用了實(shí)例。這個(gè)閉包體中可能訪問(wèn)了實(shí)例的某個(gè)屬性,例如self.someProperty,或者閉包中調(diào)用了實(shí)例的某個(gè)方法,例如self.someMethod。這兩種情況都導(dǎo)致了閉包 “捕獲" self,從而產(chǎn)生了循環(huán)強(qiáng)引用。
循環(huán)強(qiáng)引用的產(chǎn)生,是因?yàn)殚]包和類相似,都是引用類型。當(dāng)你把一個(gè)閉包賦值給某個(gè)屬性時(shí),你也把一個(gè)引用賦值給了這個(gè)閉包。實(shí)質(zhì)上,這跟之前的問(wèn)題是一樣的-兩個(gè)強(qiáng)引用讓彼此一直有效。但是,和兩個(gè)類實(shí)例不同,這次一個(gè)是類實(shí)例,另一個(gè)是閉包。
Swift 提供了一種優(yōu)雅的方法來(lái)解決這個(gè)問(wèn)題,稱之為閉包占用列表(closuer capture list)。同樣的,在學(xué)習(xí)如何用閉包占用列表破壞循環(huán)強(qiáng)引用之前,先來(lái)了解一下循環(huán)強(qiáng)引用是如何產(chǎn)生的,這對(duì)我們是很有幫助的。
下面的例子為你展示了當(dāng)一個(gè)閉包引用了self后是如何產(chǎn)生一個(gè)循環(huán)強(qiáng)引用的。例子中定義了一個(gè)叫HTMLElement的類,用一種簡(jiǎn)單的模型表示 HTML 中的一個(gè)單獨(dú)的元素:
class HTMLElement {
let name: String
let text: String?
@lazy var asHTML: () -> String = {
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 {
println("\(name) is being deinitialized")
}
}
HTMLElement類定義了一個(gè)name屬性來(lái)表示這個(gè)元素的名稱,例如代表段落的"p",或者代表?yè)Q行的"br"。HTMLElement還定義了一個(gè)可選屬性text,用來(lái)設(shè)置和展現(xiàn) HTML 元素的文本。
除了上面的兩個(gè)屬性,HTMLElement還定義了一個(gè)lazy屬性asHTML。這個(gè)屬性引用了一個(gè)閉包,將name和text組合成 HTML 字符串片段。該屬性是() -> String類型,或者可以理解為“一個(gè)沒(méi)有參數(shù),返回String的函數(shù)”。
默認(rèn)情況下,閉包賦值給了asHTML屬性,這個(gè)閉包返回一個(gè)代表 HTML 標(biāo)簽的字符串。如果text值存在,該標(biāo)簽就包含可選值text;如果text不存在,該標(biāo)簽就不包含文本。對(duì)于段落元素,根據(jù)text是"some text"還是nil,閉包會(huì)返回"<p>some text</p>"或者"<p />"。
可以像實(shí)例方法那樣去命名、使用asHTML屬性。然而,由于asHTML是閉包而不是實(shí)例方法,如果你想改變特定元素的 HTML 處理的話,可以用自定義的閉包來(lái)取代默認(rèn)值。
注意:
asHTML聲明為lazy屬性,因?yàn)橹挥挟?dāng)元素確實(shí)需要處理為HTML輸出的字符串時(shí),才需要使用asHTML。也就是說(shuō),在默認(rèn)的閉包中可以使用self,因?yàn)橹挥挟?dāng)初始化完成以及self確實(shí)存在后,才能訪問(wèn)lazy屬性。
HTMLElement類只提供一個(gè)構(gòu)造函數(shù),通過(guò)name和text(如果有的話)參數(shù)來(lái)初始化一個(gè)元素。該類也定義了一個(gè)析構(gòu)函數(shù),當(dāng)HTMLElement實(shí)例被銷毀時(shí),打印一條消息。
下面的代碼展示了如何用HTMLElement類創(chuàng)建實(shí)例并打印消息。
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
println(paragraph!.asHTML())
// prints"hello, world"
注意:
上面的paragraph變量定義為可選HTMLElement,因此我們可以賦值nil給它來(lái)演示循環(huán)強(qiáng)引用。
不幸的是,上面寫的HTMLElement類產(chǎn)生了類實(shí)例和asHTML默認(rèn)值的閉包之間的循環(huán)強(qiáng)引用。循環(huán)強(qiáng)引用如下圖所示:

實(shí)例的上一篇:Swift條件語(yǔ)句下一篇:Swift帶標(biāo)簽的語(yǔ)句