iOS中block的詳解weakSelf、strongSelf <轉自唐巧>

1
我們知道啰脚,在使用 block 的時候,為了避免產(chǎn)生循環(huán)引用实夹,通常需要使用 weakSelf 與 strongSelf橄浓,寫下面這樣的代碼:

__weak typeof(self) weakSelf = self;
[self doSomeBlockJob:^{
         __strong typeof(weakSelf) strongSelf = weakSelf; 
        if (strongSelf) {
                 ...
         }
}];

那么請問:什么時候在 block 里面用 self,不需要使用 weak self收擦?
答案
當 block 本身不被 self 持有贮配,而被別的對象持有,同時不產(chǎn)生循環(huán)引用的時候塞赂,就不需要使用 weak self 了泪勒。最常見的代碼就是 UIView 的動畫代碼,我們在使用 UIView 的 animateWithDuration:animations 方法 做動畫的時候宴猾,并不需要使用 weak self圆存,因為引用持有關系是:
UIView 的某個負責動畫的對象持有了 block block 持有了 self 因為 self 并不持有 block,所以就沒有循環(huán)引用產(chǎn)生仇哆,因為就不需要使用 weak self 了沦辙。

[UIView animateWithDuration:0.2 animations:^{ 
        self.alpha = 1;
}];

當動畫結束時,UIView 會結束持有這個 block讹剔,如果沒有別的對象持有 block 的話油讯,block 對象就會釋放掉,從而 block 會釋放掉對于 self 的持有延欠。整個內存引用關系被解除陌兑。
思考題
如果覺得上面的問題太簡單,可以想想下面兩個題目:
為什么 block 里面還需要寫一個 strong self由捎,如果不寫會怎么樣兔综? 有沒有這樣一個需求場景,block會產(chǎn)生循環(huán)引用狞玛,但是業(yè)務又需要你不能使用 weak self? 如果有软驰,請舉一個例子并且解釋這種情況下如何解決循環(huán)引用問題。
2
繼續(xù)回答昨天的問題第二問心肪。
我們知道锭亏,在使用 block 的時候,為了避免產(chǎn)生循環(huán)引用硬鞍,通常需要使用 weakSelf 與 strongSelf贰镣,寫下面這樣的代碼:

__weak typeof(self) weakSelf = self;
[self doSomeBackgroundJob:^{ 
        __strong typeof(weakSelf) strongSelf = weakSelf; 
        if (strongSelf) {
                 ...
         }
}];

那么請問:為什么 block 里面還需要寫一個 strong self呜象,如果不寫會怎么樣?
答案
在 block 中先寫一個 strong self碑隆,其實是為了避免在 block 的執(zhí)行過程中恭陡,突然出現(xiàn) self 被釋放的尷尬情況。通常情況下上煤,如果不這么做的話休玩,還是很容易出現(xiàn)一些奇怪的邏輯,甚至閃退劫狠。
我們以 AFNetworking 中 AFNetworkReachabilityManager.m 的一段代碼舉例:

__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {      
     __strong __typeof(weakSelf)strongSelf = weakSelf; 
     strongSelf.networkReachabilityStatus = status;
     if (strongSelf.networkReachabilityStatusBlock) {         
        strongSelf.networkReachabilityStatusBlock(status); 
      }
};

如果沒有 strongSelf 的那行代碼拴疤,那么后面的每一行代碼執(zhí)行時,self 都可能被釋放掉了独泞,這樣很可能造成邏輯異常呐矾。
特別是當我們正在執(zhí)行 strongSelf.networkReachabilityStatusBlock(status); 這個 block 閉包時,如果這個 block 執(zhí)行到一半時 self 釋放懦砂,那么多半情況下會 Crash蜒犯。
這里有一篇文章詳細解釋了這個問題:https://dhoerl.wordpress.com/2013/04/23/i-finally-figured-out-weakself-and-strongself/
另外,還有讀者提了兩個有意思的問題荞膘,大家可以思考一下:
提問:“數(shù)組” 和 “字典” 的 enumeratXXXUsingBlock: 是否要使用 weakSelf 和 strongSelf 呢罚随?
提問:block 里 strong self 后,block 不是也會持有 self 嗎羽资?而 self 又持有 block 淘菩,那不是又循環(huán)引用了?
3
有沒有這樣一個需求場景屠升,block 會產(chǎn)生循環(huán)引用潮改,但是業(yè)務又需要你不能使用 weak self? 如果有,請舉一個例子并且解釋這種情況下如何解決循環(huán)引用問題腹暖。
答案
需要不使用 weak self 的場景是:你需要構造一個循環(huán)引用进陡,以便保證引用雙方都存在。比如你有一個后臺的任務微服,希望任務執(zhí)行完后,通知另外一個實例缨历。在我們開源的 YTKNetwork 網(wǎng)絡庫的源碼中以蕴,就有這樣的場景。
在 YTKNetwork 庫中辛孵,我們的每一個網(wǎng)絡請求 API 會持有回調的 block丛肮,回調的 block 會持有 self,而如果 self 也持有網(wǎng)絡請求 API 的話魄缚,我們就構造了一個循環(huán)引用宝与。雖然我們構造出了循環(huán)引用焚廊,但是因為在網(wǎng)絡請求結束時,網(wǎng)絡請求 API 會主動釋放對 block 的持有习劫,因此咆瘟,整個循環(huán)鏈條被解開,循環(huán)引用就被打破了诽里,所以不會有內存泄漏問題袒餐。代碼其實很簡單,如下所示:

- (void)clearCompletionBlock {
     // nil out to break the retain cycle.
     self.successCompletionBlock = nil; 
    self.failureCompletionBlock = nil;
}

總結來說谤狡,解決循環(huán)引用問題主要有兩個辦法:
第一個辦法是「事前避免」灸眼,我們在會產(chǎn)生循環(huán)引用的地方使用 weak 弱引用,以避免產(chǎn)生循環(huán)引用墓懂。 第二個辦法是「事后補救」焰宣,我們明確知道會存在循環(huán)引用,但是我們在合理的位置主動斷開環(huán)中的一個引用捕仔,使得對象得以回收匕积。 思考題
下期的問題是:weak 變量在引用計數(shù)為 0 時,會被自動設置成 nil逻澳,這個特性是如何實現(xiàn)的闸天?
4
weak 變量在引用計數(shù)為0時,會被自動設置成 nil斜做,這個特性是如何實現(xiàn)的苞氮?
答案
在 Friday QA 上,有一期專門介紹 weak 的實現(xiàn)原理瓤逼。https://mikeash.com/pyblog/friday-qa-2010-07-16-zeroing-weak-references-in-objective-c.html
《Objective-C高級編程》一書中也介紹了相關的內容笼吟。
簡單來說,系統(tǒng)有一個全局的 CFMutableDictionary 實例霸旗,來保存每個對象的 weak 指針列表贷帮,因為每個對象可能有多個 weak 指針,所以這個實例的值是 CFMutableSet 類型诱告。
剩下我們要做的撵枢,就是在引用計數(shù)變成 0 的時候,去這個全局的字典里面精居,找到所有的 weak 指針锄禽,將其值設置成 nil。如何做到這一點呢靴姿?Friday QA 上介紹了一種類似 KVO 實現(xiàn)的方式沃但。當對象存在 weak 指針時,我們可以將這個實例指向一個新創(chuàng)建的子類佛吓,然后修改這個子類的 release 方法宵晚,在 release 方法中垂攘,去從全局的 CFMutableDictionary 字典中找到所有的 weak 對象,并且設置成 nil淤刃。我摘抄了 Friday QA 上的實現(xiàn)的核心代碼晒他,如下:

Class subclass = objc_allocateClassPair(class, newNameC, 0);
Method release = class_getInstanceMethod(class, @selector(release));
Method dealloc = class_getInstanceMethod(class, @selector(dealloc));
class_addMethod(subclass, @selector(release), (IMP)CustomSubclassRelease, method_getTypeEncoding(release));
class_addMethod(subclass, @selector(dealloc), (IMP)CustomSubclassDealloc, method_getTypeEncoding(dealloc));
objc_registerClassPair(subclass);

當然,這并不代表蘋果官方是這么實現(xiàn)的钝凶,因為蘋果的這部分代碼并沒有開源仪芒。《Objective-C高級編程》一書中介紹了 GNUStep 項目中的開源代碼耕陷,思想也是類似的掂名。所以我認為雖然實現(xiàn)細節(jié)會有差異,但是大致的實現(xiàn)思路應該差別不大哟沫。
全文完饺蔑。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市嗜诀,隨后出現(xiàn)的幾起案子猾警,更是在濱河造成了極大的恐慌,老刑警劉巖隆敢,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件发皿,死亡現(xiàn)場離奇詭異,居然都是意外死亡拂蝎,警方通過查閱死者的電腦和手機穴墅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來温自,“玉大人玄货,你說我怎么就攤上這事〉棵冢” “怎么了松捉?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長馆里。 經(jīng)常有香客問我隘世,道長,這世上最難降的妖魔是什么鸠踪? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任丙者,我火速辦了婚禮,結果婚禮上慢哈,老公的妹妹穿的比我還像新娘。我一直安慰自己永票,他們只是感情好卵贱,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布滥沫。 她就那樣靜靜地躺著,像睡著了一般键俱。 火紅的嫁衣襯著肌膚如雪兰绣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天编振,我揣著相機與錄音缀辩,去河邊找鬼。 笑死踪央,一個胖子當著我的面吹牛臀玄,可吹牛的內容都是我干的。 我是一名探鬼主播畅蹂,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼健无,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了液斜?” 一聲冷哼從身側響起累贤,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎少漆,沒想到半個月后臼膏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡示损,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年渗磅,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屎媳。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡夺溢,死狀恐怖,靈堂內的尸體忽然破棺而出烛谊,到底是詐尸還是另有隱情风响,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布丹禀,位于F島的核電站状勤,受9級特大地震影響,放射性物質發(fā)生泄漏双泪。R本人自食惡果不足惜持搜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望焙矛。 院中可真熱鬧葫盼,春花似錦、人聲如沸村斟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至孩灯,卻和暖如春闺金,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背峰档。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工败匹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人讥巡。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓掀亩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親尚卫。 傳聞我的和親對象是個殘疾皇子归榕,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內容