晚上,吃了幾顆金平糖,來寫一下最近看到的東西。
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
這時候只要在 MYObject
的 capture
這個 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 有沒有少了這些東西吧