ARC
Swift

Swift, closure 和 capture list

More than 1 year has passed since last update.

晚上,吃了幾顆金平糖,來寫一下最近看到的東西。

Closure Capture List

在 Cocoa framework 中的 ARC 不全然是會完完全全的幫開發者釋放掉不會用的物件。

在 Objective-C 的環境中,我們會宣告 weak self 來使用:

__weak SomeObject *weakSelf = self;

但是在 Swift 中,多了個叫做「Closure Capture List」的語法, 可以幫你產生這個東西,可以直接寫在 closure 的大括號裡面,看起來更簡潔了。

緣起

當一個物件的 成員變數 或是 成員函數 有在 closure 裡面被呼叫到,這個物件就會被 closure capture 起來。即使當這個物件的 retain count 達到 0 ,這個物件還是不會被釋放。

這種事情發生了之後就會造成 memory leak 。

當不再使用這個物件之後,也無法去將它釋放掉、他也會就像僵屍般的無主地遊走在那邊。

範例

來從範例開始,我現在有個叫 MyObject 的 class

class MYObject: NSObject {

    var text = "my text"

    deinit {
        // 為了可以清楚有被釋放,所以在釋放時印出字串
        debugPrint("deinit")
    }

    func myMethod() {
        debugPrint("myMethod")
    }

    // 主角就是這個叫 capture 的 closure ,為了可以存取到 self ,所以加上 lazy 這個前綴
    lazy var capture: ()->() = {
        print(self.text)
        self.myMethod()
    }
}

接著再另外一個範圍內先執行以下的程式碼:

var myObject = MYObject()
myObject = nil

這時候因為沒有執行到有關 closure 的程式碼,所以當走完第二行的之後,因為沒有參照就被釋放了,這時候可以在 Xcode 的 console 看到這段字:

"deinit"

執行 closure

var myObject: MYObject? = MYObject()
myObject?.capture()
myObject = nil

這時候執行完就會發現 console 裡面會印出

my text
"myMethod"

而不會有 "deinit" 跑出來。這樣就代表在執行完 capture 這個 closure 之後,原先 myObject 中指向的那個 object 並不會 真正的被 ARC 收收走。

加入 closure capture list

這時候只要在 MYObjectcapture 這個 closure 裡面加入一個 closure capture list ,並在裡面放入 owned self 即可:

lazy var capture: ()->() = { [unowned self] in
    print(self.text)
    self.myMethod()
}

這時候在執行前段的程式碼

var myObject: MYObject? = MYObject()
myObject?.capture()
myObject = nil

這時候就會得到如下的 output :

my text
"myMethod"
"deinit"

就會發現這個 object 有好好的被釋放掉了

weak 和 unowned 的差別

在 capture list 裡面可以放兩種變數,一種是 weak ,另外一種則是 unowned ,這樣成對的存在。使用實機則如下:

  • weak 是用於 Optional type (?) 變數
  • unowned 則是用在隱性開箱(implicitly unwrapped)或是被宣告為一定會有值的變數 (!)

實際的使用方式則要依據 class 的設計來變化,例如:

  • delegate 變數通常是 Optional type ,就會這樣寫: [weak delegate = self.delegate]

應用

以上,這個會在網路存取的 closure 大量會被使用到,不妨檢查一下自己的 code 有沒有少了這些東西吧 :sushi:

參考資料