Swift中的Weak Strong Dance,swiftweak
親愛的部落格園的關注著博主文章的朋友們告訴你們一個很不幸的訊息哦,
這篇文章將會是博主在部落格園發表的最後一篇文章咯,
因為之後的文章博主只會發布到這裡哦 http://daiweilai.github.io/
新部落格排版布局更好,閱讀體驗更佳,歡迎吐槽、留言、訂閱喲
馬上又要過年了,誒,再也不能像當初那樣無恥地逗利是了(我們廣東的方言討紅包的意思)
圖1
圖2
看來今年沒利了
誰讓哥已經工作了呢。
公司今年的開發工作單位算是完結了,蘋果又極不負(hǎo)責(yàng)任(de)地放聖誕不審核了,所以這半個月就該清閑下來了。
博主掐指一算,Swift已經養到2.1了,並且也開源了,這樣看來Swift也夠肥了,文法也絕逼不會再有大改動了,是該再次抓起來了。
為什麼說再呢,其實在當初Swift Beta版本的時候,我們專案經理嘗試了一下palyground後,一拍手,棒棒噠,”我們用Swift開發接下來的項目吧”,然後就是Swift1.0 … 2.0,每一次升級,每一次文法更迭,每一次XCode開啟的那一刻都是滿江紅,那觸目驚心的場面無數次讓博主受盡折磨。最後慶幸的是,這個項目死了,哦耶!
好吧,重新撿起Swift吧,今天的這篇文章是一篇激情翻譯大作,而且本人的英文水平獲得了原作者充分的肯定:
圖3
看到了吧,毫無障礙地交流!!我們愉快地聊了很久~~ ,激情四射,biu biu ~
好吧回到博文的主題中來,這次我們說說“Weak/Strong Dance”
在block中解決循環參考要追尋的2011 WWDC Session #322 當時驚豔的代碼是這樣的:
- (void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:_observer];}- (void)loadView{ [super loadView]; __weak TestViewController *wself = self; _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey" object:nil queue:nil usingBlock:^(NSNotification *note) { TestViewController *sself = wself; [sself dismissModalViewControllerAnimated:YES]; }];}
或者可以看看AFNetWorking是這樣用block的:
__weak __typeof(self)weakSelf = self;AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {__strong __typeof(weakSelf)strongSelf = weakSelf;strongSelf.networkReachabilityStatus = status;if (strongSelf.networkReachabilityStatusBlock) {strongSelf.networkReachabilityStatusBlock(status);}};
我們都很熟悉Objective-C中的“weak/strong dance”,但是寂寞無聊的我突然就很想知道Swift語言中該怎麼做呢?是否存在傳說中的最佳實務呢?
好啦,翻譯開始!
原文
首先,我們祭出一個在閉包中沒有使用weak的引用導致的循環參考的例子
class C { let name: String var block: (() -> Void)? init(name: String) { self.name = name block = { self.doSomething() } } deinit { print("Destroying \(name)") } func doSomething() { print("Doing something for \(name)") }}var c = C(name: "one")print(c.name)c = C(name: "two")print(c.name)
輸出
onetwo
這是一個巨基礎又明顯的循環參考的例子,self -> block ->self
所以,deinit
方法是絕逼不會被執行的,即使你把 c
重新指向nil
或者其他的執行個體,c
也不會被銷毀,這就是頑固又調皮的循環參考了,尤其是當你把c
指向nil
之後,這個對象你就再也引用不了了,它就靜靜的躺在堆記憶體裡面,遺世而獨立,然後你就堆記憶體泄露了,然後你就淡淡的憂傷從下體傳來 ~ ~沒有然後了
其實Swift中閉包的參數列表(Capture List) 已經能夠很好的讓你擷取一個weak self
來避免循環參考了,但這還達不到我們的要求,只有weak
是構不成“weak/strong dance”滴。
使用閉包參數列表
class C { let name: String var block: (() -> Void)? init(name: String) { self.name = name block = { [weak self] in // <-- 這裡做出一些改變 self?.doSomething() } } deinit { print("Destroying \(name)") } func doSomething() { print("Doing something for \(name)") }}var c = C(name: "one")print(c.name)c = C(name: "two")print(c.name)
輸出
oneDestroying onetwo
這樣就沒有循環參考啦~
在閉包中使用
self
使用 [weak self]
有一個細節,就是self
在閉包中會變成Optional
從上面的代碼中self?.doSomething()
就可以看出來了。
但是如果你在這個閉包中狂轟亂炸的使用self?
(多次使用self?
),問題就來了,因為這個self?
是一個弱引用的,那麼你沒法確定在這個閉包中所有的self?
操作都能執行完畢,畢竟若引用的self
可能隨時都掛掉,然後怒舉一個栗子:
圖4
class C { deinit { println("Destroying C") } func log(msg: String) { println(msg) } func doClosure() { dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] in self?.log("before sleep") usleep(500) self?.log("after sleep") } }}var c: C? = C() // Optional, so we can set it to nilc?.doClosure()dispatch_async(dispatch_get_global_queue(0, 0)) { usleep(100) c = nil // This will dealloc c}dispatch_main()
輸出
before sleepDestroying C
小提示:當然一般來說在dispatch_async()
中你不必擔心會有循環參考,因為self並不會持有dispatch_async()
的block,所以上述的代碼中並不會真的導致循環參考,如果你的閉包並不是很注重結果的,那麼self
為nil
閉包就不會再執行,這個還是挺有用的。
上述的代碼中不會列印after sleep
,因為self?
在列印這句話之前已經掛掉了。
通常這種無根之源的bug可以把你整的半死。所以通常遇到這種閉包中多次試用self?
的操作的時候,一般會把self?
變為又粗又壯的strong self
,(博主也是又粗又壯的,捂臉~~)這就是傳說中的“weak/strong dance”,這個舞蹈,額,什麼鬼,為什麼把這個技術叫做dance啊,我覺得叫做美隊解禁奧義技
還不錯,婦聯裡面的美國隊長也是由weak
變成strong
的嘛~,好吧,扯太遠,菊花都扯疼了,我們這是在技術翻譯呢!要嚴肅!要尊重原作者!我們還是叫dance吧,有了這個dance之後呢,我們就能確保一旦閉包被執行,self
就不會為nil
。
但是,就像文章開頭說的,對於在Swift
的“weak/strong dance”中變回strong的這部分的最佳實務是什麼我也不是很確定的。。。
擷取強引用的一些想法使用可選綁定
if let
func doClosure() { dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] in if let strongSelf = self { // <-- 這裡就是精髓了 strongSelf.log("before sleep") usleep(500) strongSelf.log("after sleep") } }}// or in Swift 2, using `guard let`:dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] in guard let strongSelf = self else { return } // <-- 這裡就是精髓了 strongSelf.log("before sleep") usleep(500) strongSelf.log("after sleep")}
輸出
before sleepafter sleepDestroying C
優點:
- 很明顯的看出整個操作的流程
- 在閉包中拿到了非可選的本地變數
缺點:
- 很不幸的是我們不能
if let self = self
,因為self
是常量,不可變,這樣的話我們就只能if let strongSelf = self
在閉包的範圍中都要使用醜陋的strongSelf
了。
- 在swift的閉包中,如果你沒有試用
strongSelf
而是使用了self
,這樣編譯器會警告!因為這個時候self
是可選的嘛,相比較OC中,就不會警告了。(這句話哥讀了21遍,為什麼覺得這個不是缺點呢)
使用
withExtendedLifetime
在Swift的標準庫中有一個函數:withExtendedLifetime()
,感覺就像Apple這個金魚佬故意誘導我們使用這個函數來實現“weak/strong dance”。
/// Evaluate `f()` and return its result, ensuring that `x` is not/// destroyed before f returns.func withExtendedLifetime<T, Result>(x: T, @noescape _ f: () -> Result) -> Result
那就試試
func doClosure() { dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] in withExtendedLifetime(self) { self!.log("before sleep") usleep(500) self!.log("after sleep") } }}
優點:
缺點:
self
還是他媽可選的,調用方法什麼的還是要!?,還是要解包,博主突然想起自己的一個技能:單手解,呵呵
自訂一個
withExtendedLifetime()
這個方法是 @jtbandes 這哥們想的,大概會是這樣:
extension Optional { func withExtendedLifetime(body: Wrapped -> Void) { if let strongSelf = self { body(strongSelf) } }}// Then:func doClosure() { dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] () -> Void in self.withExtendedLifetime { $0.log("before sleep") usleep(500) $0.log("after sleep") } return }}
優點:
- Follows naming conventions set by the standard library.(原文) 感覺沒優點~
缺點:
strongSelf
變成了使用$0
,博主認為哦,還是很醜陋,並且可讀性更差了
- In this case, I had to add some extra type info to the
dispatch_async()
closure. I’m not totally sure why. 不知道他說什麼鬼~
翻譯至此結束了
後記
關於Swift中 “Weak/Strong Dance”,中的Weak部分,大家可以參閱喵大的這篇文章 記憶體管理,weak 和 unowned 。
用回了一個多禮拜的Swift真是感受頗多,雖然Xcode在寫Swift就像純文字編輯器一樣,但是我還是想說一句:Swift真™安全!想crash都難咯。
還有 Happy Christmas!
收筆,走人。