對 Strong-Weak Dance的思考

在使用 Block 時,除了使用 __weak 修飾符避免循環引用外,還有一點常常容易忘記。蘋果把它稱爲:「Strong-Weak Dance」。git

問題來源

這是一種 強引用 --> 弱引用 --> 強引用 的變換過程。在弄明白爲何要如此大費周章以前,咱們首先來看看通常的寫法會有什麼問題。github

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

這種寫法能夠避免循環引用,可是咱們要考慮這樣的問題:swift

假設 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,避免了對象的銷燬。app

思考

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

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

A:會的。引用計數描述的是對象而不是指針。這句話的意思是:ui

sself 強引用 wself 指向的那個對象spa

所以對象的引用計數會增長一個。線程

  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
相關文章
相關標籤/搜索