位置:首頁 > 高級語言 > Swift教學 > Swift解決閉包引起的循環強引用

Swift解決閉包引起的循環強引用

解決閉包引起的循環強引用

在定義閉包時同時定義捕獲列表作為閉包的一部分,通過這種方式可以解決閉包和類實例之間的循環強引用。捕獲列表定義了閉包體內捕獲一個或者多個引用類型的規則。跟解決兩個類實例間的循環強引用一樣,聲明每個捕獲的引用為弱引用或無主引用,而不是強引用。應當根據代碼關係來決定使用弱引用還是無主引用。


注意:
Swift 有如下要求:隻要在閉包內使用self的成員,就要用self.someProperty或者self.someMethod(而不隻是somePropertysomeMethod)。這提醒你可能會不小心就捕獲了self
 

定義捕獲列表

捕獲列表中的每個元素都是由weak或者unowned關鍵字和實例的引用(如selfsomeInstance)成對組成。每一對都在方括號中,通過逗號分開。

捕獲列表放置在閉包參數列表和返回類型之前:

@lazy var someClosure: (Int, String) -> String = {
    [unowned self] (index: Int, stringToProcess: String) -> String in
    // closure body goes here
}

如果閉包冇有指定參數列表或者返回類型,則可以通過上下文推斷,那麼可以捕獲列表放在閉包開始的地方,跟著是關鍵字in

@lazy var someClosure: () -> String = {
    [unowned self] in
    // closure body goes here
}

弱引用和無主引用

當閉包和捕獲的實例總是互相引用時並且總是同時銷毀時,將閉包內的捕獲定義為無主引用。

相反的,當捕獲引用有時可能會是nil時,將閉包內的捕獲定義為弱引用。弱引用總是可選類型,並且當引用的實例被銷毀後,弱引用的值會自動置為nil。這使我們可以在閉包內檢查它們是否存在。


注意:
如果捕獲的引用絕對不會置為nil,應該用無主引用,而不是弱引用。
 

前麵的HTMLElement例子中,無主引用是正確的解決循環強引用的方法。這樣編寫HTMLElement類來避免循環強引用:

class HTMLElement {

    let name: String
    let text: String?

    @lazy var asHTML: () -> 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 {
        println("\(name) is being deinitialized")
    }

}

上麵的HTMLElement實現和之前的實現一致,隻是在asHTML閉包中多了一個捕獲列表。這裡,捕獲列表是[unowned self],表示“用無主引用而不是強引用來捕獲self”。

和之前一樣,我們可以創建並打印HTMLElement實例:

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
println(paragraph!.asHTML())
// prints "<p>hello, world</p>"

使用捕獲列表後引用關係如下圖所示:

這一次,閉包以無主引用的形式捕獲self,並不會持有HTMLElement實例的強引用。如果將paragraph賦值為nilHTMLElement實例將會被銷毀,並能看到它的析構函數打印出的消息。

paragraph = nil
// prints "p is being deinitialized"