Swift中的Weak/Strong Dance
馬上又要過年了,誒,再也不能像當初那樣無恥地逗利是了(我們廣東的方言討紅包的意思)
圖1
圖2
看來今年沒利了
誰讓哥已經工作了呢。
公司今年的開發任務算是完結了,蘋果又極不負(hǎo)責(yàng)任(de)地放聖誕不稽核了,所以這半個月就該清閒下來了。
博主掐指一算,Swift已經養到2.1了,並且也開源了,這樣看來Swift也夠肥了,語法也絕逼不會再有大改動了,是該再次抓起來了。
為什麼說再呢,其實在當初Swift Beta版本的時候,我們專案經理嘗試了一下palyground後,一拍手,棒棒噠,"我們用Swift開發接下來的專案吧",然後就是Swift1.0 … 2.0,每一次升級,每一次語法更迭,每一次XCode開啟的那一刻都是滿江紅,那觸目驚心的場面無數次讓博主受盡折磨。最後慶幸的是,這個專案死了,哦耶!
好吧,重新撿起Swift吧,今天的這篇文章是一篇激情翻譯大作。
好吧回到博文的主題中來,這次我們說說“Weak/Strong Dance”
在block中解決迴圈引用要追尋的2011 WWDC Session #322{:target="_blank"} 當時驚豔的程式碼是這樣的:
- (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{:target="_blank"} 是這樣用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語言中該怎麼做呢?是否存在傳說中的最佳實踐呢?
好啦,翻譯開始!
原文{:target="_blank"}
首先,我們祭出一個在閉包中沒有使用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)
輸出
one
two
這是一個巨基礎又明顯的迴圈引用的例子,self -> block ->self
所以,deinit
方法是絕逼不會被執行的,即使你把 c
重新指向nil
或者其他的例項,c
也不會被銷燬,這就是頑固又調皮的迴圈引用了,尤其是當你把c
指向nil
之後,這個物件你就再也引用不了了,它就靜靜的躺在堆記憶體裡面,遺世而獨立,然後你就堆記憶體洩露了,然後你就淡淡的憂傷從下體傳來 ~ ~沒有然後了
其實Swift中閉包的引數列表(Capture List{:target="_blank"}) 已經能夠很好的讓你獲取一個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)
輸出
one
Destroying one
two
這樣就沒有迴圈引用啦~
在閉包中使用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 nil
c?.doClosure()
dispatch_async(dispatch_get_global_queue(0, 0)) {
usleep(100)
c = nil // This will dealloc c
}
dispatch_main()
輸出
before sleep
Destroying 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 sleep
after sleep
Destroying 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")
}
}
}
優點:
- 閉包中不再需要使用醜陋的
strongSelf
了
缺點:
-
self
還是他媽可選的,呼叫方法什麼的還是要!?,還是要解包,博主突然想起自己的一個技能:單手解,呵呵
自定義一個withExtendedLifetime()
這個方法是 @jtbandes{:target="_blank"} 這哥們想的,大概會是這樣:
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{:target="_blank"} 。
用回了一個多禮拜的Swift真是感受頗多,雖然Xcode在寫Swift還是像純文字編輯器一樣,但是我還是想說一句:Swift真™安全!想crash都難咯。
收筆,走人。
相關文章
- 對 Strong-Weak Dance的思考
- 對 Strong-Weak Dance 的思考
- 深入理解 weak-strong dance
- iOS strong weak unowned引用iOS
- iOS5 strong, weak, unsafe_unretained ARCiOSAI
- 【iOS】用strong和weak來修飾成員變數的對比iOS變數
- 被無數人寫過的assign,retain,strong,weak,unsafe_unretained,還有copyAI
- [論文閱讀] Aligner@ Achieving Efficient Alignment through Weak-to-Strong Correction
- Swift 記憶體管理之 weak 與 unownedSwift記憶體
- Last danceAST
- iOS中copy和strong的個人理解iOS
- Swift之你真的知道為什麼使用weak嗎?Swift
- Rust 程式設計影片教程(進階)——015_3 檢視 strong_count 和 weak_count 的改變Rust程式設計
- Rust 程式設計視訊教程(進階)——015_3 檢視 strong_count 和 weak_count 的改變Rust程式設計
- swift 中的 ??Swift
- Swift 中的 @autoclosureSwift
- Swift中的反射Swift反射
- swift 中的 lazySwift
- swift 中的 whereSwift
- iOS中assign和weak修飾符的區別iOS
- iOS strong和copy的區別iOS
- B,strong,I,em的區別
- Swift 4 中的字串Swift字串
- Swift中的模式匹配Swift模式
- Swift 中的屬性Swift
- Swift中的協議Swift協議
- swift 中的 @UIApplicationMainSwiftUIAPPAI
- swift 中的 SelectorSwift
- Swift中的SelectorSwift
- Swift 中的 RuntimeSwift
- Object Runtime -- WeakObject
- iOS __weak、__block使用iOSBloC
- weak實現原理
- weak 弱引用的實現方式
- weak和assign的區別
- Swift 4 中的泛型Swift泛型
- Swift 中的訪問控制Swift
- swift 中的 AnyObject 和 AnySwiftObject