對 Strong-Weak Dance的思考

bestswifter發表於2019-03-04

在使用 Block 時,除了使用 __weak 修飾符避免迴圈引用外,還有一點經常容易忘記。蘋果把它稱為:“Strong-Weak Dance”。

問題來源

這是一種 強引用 –> 弱引用 –> 強引用 的變換過程。在弄明白為什麼要如此大費周章之前,我們首先來看看一般的寫法會有什麼問題。

__weak MyViewController *wself = self;
self.completionHandler = ^(NSInteger result) {
[wself.property removeObserver: wself forKeyPath:@"pathName"];
};
複製程式碼

這種寫法可以避免迴圈引用,但是我們要考慮這樣的問題:

假設 block 被放在子執行緒中執行,而且執行過程中 self 在主執行緒被釋放了。由於 wself 是一個弱引用,因此會自動變為 nil。而在 KVO 中,這會導致崩潰。

Strong-Weak Dance

解決以上問題的方法很簡單,新增一行程式碼即可:

__weak MyViewController *wself = self;
self.completionHandler = ^(NSInteger result) {
__strong __typeof(wself) sself = wself; // 強引用一次
[sself.property removeObserver: sself forKeyPath:@"pathName"];
};
複製程式碼

這樣一來,self 所指向物件的引用計數變成 2,即使主執行緒中的 self 因為超出作用於而釋放,物件的引用計數依然為 1,避免了物件的銷燬。

思考

在和小夥伴的討論過程中,他提出了幾個問題。雖然都不難,但是有利於把各種知識融會貫通起來。

  1. Q:下面這行程式碼,將一個弱引用的指標賦值給強引用的指標,可以起到強引用效果麼?
__strong __typeof(wself) sself = wself;
複製程式碼

A:會的。引用計數描述的是物件而不是指標。這句話的意思是:

sself 強引用 wself 指向的那個物件

因此物件的引用計數會增加一個。

  1. Q:block 內部定義了sself,會不會因此強引用了 sself

A:不會。block 只有截獲外部變數時,才會引用它。如果是內部新建一個,則沒有任何問題。

  1. Q:如果在 block 內部沒有強引用,而是通過 if 判斷,是不是也可以,比如這樣寫:
__weak MyViewController *wself = self;
wself.completionHandler = ^(NSInteger result) {
if (wself) { // 只有當 wself 不為 nil 時,才執行以下程式碼
[wself.property removeObserver: wself forKeyPath:@"pathName"];
}
};
複製程式碼

A:不可以!考慮到多執行緒執行,也許在判斷的時候,self 還沒釋放,但是執行 self 裡面的程式碼時,就剛好釋放了。

  1. Q:那按照這個說法,block 內部強引用也沒用啊。也許 block 執行以前,self 就釋放了。

A:有用!如果在 block 執行以前,self 就釋放了,那麼 block 的引用計數降為 0,所以自己就會被釋放。這樣它根本就不會被執行。另外,如果執行一個為 nil 的閉包會導致崩潰。

  1. Q:如果在執行 block 的過程中,block 被釋放了怎麼辦?

A:簡單來說,block 還會繼續執行,但是它捕獲的指標會具有不確定的值,詳細內容請參考這篇文章

@strongify 和 @weakify

這是 ReactiveCocoa 中定義的一個巨集。一般可以這樣使用:

@weakify(self);
self.completionHandler = ^(NSInteger result) {
@strongify(self);
[self.property removeObserver: sself forKeyPath:@"pathName"];
};
複製程式碼

本文並非分析它們的實現原理,所以就簡單解釋兩點:

  1. 這裡的“@”沒有任何用處,僅表示強調,這個巨集實際上包含了一個空的 AutoreleasePool,這也就是為什麼一定要加上“@”。

  2. 它的原理還是和之前一樣,生成了一段形如 __weak MyViewController *wself = self; 這種格式的程式碼:

#define rac_strongify_(INDEX, VAR) \
__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
複製程式碼

Swift 中的情況

感謝 @Cyrus_dev 的提醒,在 Swift 中也有 Strong-Weak Dance 的概念。最簡單的方法就是直接沿用 OC 的思路:

self.completionHandler = { [weak self] in
if let strongSelf = self {
/// ....
}
};
複製程式碼

這種寫法的缺點在於,我們不能寫 if let self = self,因此需要重新定義一個變數 strongSelf,命名方式顯得不夠優雅。

除此以外還可以使用 Swift 標準庫提供的函式 withExtendedLifetime

self.completionHandler = { [weak self] in
withExtendedLifetime(self) {
/// ...
}
};
複製程式碼

這種寫法的缺點在於,self 依然是可選型別的,還需要把它解封后才能使用。

最後,還有一種解決方案是自定義 withExtendedLifetime函式:

extension Optional {
func withExtendedLifetime(body: T -> Void) {
if let strongSelf = self {
body(strongSelf)
}
}
}
複製程式碼

至於這種寫法是否更加優雅,就見仁見智了:

self.completionHandler = { [weak self] in
self.withExtendedLifetime {
/// 這裡用 $0 表示 self
}
};
複製程式碼

參考資料

  1. What happens when a block is set to nil during its execution?
  2. The Weak/Strong Dance in Swift

相關文章