Block里面的weak-strong理解

快年底了固阁,想對一些之前有點模糊的概念進行總結,所以寫了總結筆記。主要參考了這幾篇文章:
Block技術中的weak-strong
什么時候在 block 中不需要使用 weakSelf
為什么 weakSelf 需要配合 strong self 使用
block 什么時候需要構造循環(huán)引用

一.循環(huán)引用例子

 #import "ViewController.h"

@interface ViewController(){
   id _observer;
  }

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"kThemeChangeNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
    NSLog(@"%@", self);
    }];

  }

- (void)dealloc {
    if (_observer) {
        [[NSNotificationCenter defaultCenter] removeObserver:_observer];
    }
}

在這段代碼中,我們向通知中心注冊了一個觀察者,然后在dealloc時殉了,解除該注冊,看起來很正常拟枚,但這里存在循環(huán)引用薪铜。

a.消息通知block引用了self,這里self對象被block保留一次
b._observer又retain該block的一份拷貝,通知中心又持有_observer.
c.只要_observer對象還沒有被解除注冊恩溅,block就會被通知中心一直持有隔箍,從而self就不會被釋放,dealloc也不會被調用
d.但我們有希望在dealloc中通過removeObserver來解除注冊以消除通知中心對_observer/block的保留次數(shù)脚乡。
e.同時_observer是self所在類中定義賦值蜒滩,被self retain
f.總結:因為self要想調用dealloc就必須等通知中心移除注冊,釋放掉_observer,但是要想通知中心移除注冊奶稠、釋放掉_observer就必須調用dealloc.這樣相互等待就變成了死循環(huán)俯艰。這里即便self不持有_observer也會產生循環(huán)引用問題,_observer只是為了告訴通知中心移除哪個注冊。

二锌订、解決方法:

1.通過weakSelf和strongSelf

@interface ViewController(){
    id _observer;
}
@end

@implementation ViewController

 #pragma mark --- life circle

- (void)viewDidLoad {
    [super viewDidLoad];

    __weak __typeof (self)weakSelf = self;
    _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"kThemeChangeNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        if (strongSelf) {
             NSLog(@"%@", strongSelf);
        }
    }];
}

- (void)dealloc {
    if (_observer) {
        [[NSNotificationCenter defaultCenter] removeObserver:_observer];
    }
}

在這段代碼中竹握,通過在block之前定義對self的弱引用,來解決了循環(huán)引用問題辆飘。

a.在block之前定義對self的一個弱引用weakSelf,因為是弱引用啦辐,所以self被釋放時weakSelf會變?yōu)閚il;
b.在block中引用該弱引用,考慮到多線程情況蜈项,通過強引用strongSelf來引用該弱引用昧甘,這時如果self不為nil就會retain self,以防止在block內部使用過程中self被釋放。
c.在block塊中使用該強引用strongSelf战得,注意對strongSelf進行nil檢測,因為多線程在弱引用weakSelf對強引用strongSelf賦值時庸推,弱引用weakSelf可能已經為nil了
d.強引用strongSelf在block作用域結束之后常侦,自動釋放。

三贬媒、block不需要使用weakSelf情況

block本身不被self持有聋亡,而被別的對象持有,同時不產生循環(huán)引用的時候际乘,就不需要weakSelf.最常見的代碼就是UIView的動畫代碼坡倔。我們在使用UIViewanimateWithDuration:animations方法做動畫的時候,并不需要使用weakSelf, 因為引用的持有關系是:

a.UIView的某個負責動畫的對象持有了block
b.block持有了self

因為self并沒有持有block,所以就不存儲循環(huán)引用,因此就不需要使用weakSelf;

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

當動畫結束時罪塔,UIView會結束持有這個block投蝉,如果沒有別的對象持有block的話,block就會釋放掉征堪,從而block就會釋放對于self的持有瘩缆,整個內存引用關系被解除。

四.weakSelf 為什么需要strongSelf配合使用

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;
    
    if (strongSelf) {
        strongSelf.networkReachabilityStatus = status;
        if (strongSelf.networkReachabilityStatusBlock) {
            strongSelf.networkReachabilityStatusBlock(status);
        }
    }
};

如果沒有 strongSelf 的那行代碼斤儿,那么后面的每一行代碼執(zhí)行時,self 都可能被釋放掉了腮考,這樣很可能造成邏輯異常雇毫。
特別是當我們正在執(zhí)行 strongSelf.networkReachabilityStatusBlock(status); 這個 block 閉包時,如果這個block執(zhí)行到一半時 self釋放踩蔚,那么多半情況下會 Crash棚放。

五.block需要構造循環(huán)引用

需要不使用weak self的場景是:你需要構造一個循環(huán)引用,以便保證引用雙方都存在馅闽。比如你有一個后臺的任務飘蚯,希望任務執(zhí)行完后,通知另外一個實例福也。比如開源的YTKNetwork 網絡庫的源碼中局骤,就有這樣的場景。

YTKNetwork庫中暴凑,我們的每一個網絡請求API 會持有回調的block峦甩,回調的 block會持有self,而如果 self也持有網絡請求API的話现喳,我們就構造了一個循環(huán)引用凯傲。雖然我們構造出了循環(huán)引用,但是因為在網絡請求結束時嗦篱,網絡請求 API 會主動釋放對block的持有冰单,因此,整個循環(huán)鏈條被解開灸促,循環(huán)引用就被打破了诫欠,所以不會有內存泄漏問題涵卵。代碼其實很簡單,如下所示:

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

總結來說荒叼,解決循環(huán)引用問題主要有兩個辦法:

第一個辦法是「事前避免」轿偎,我們在會產生循環(huán)引用的地方使用 weak 弱引用,以避免產生循環(huán)引用甩挫。
第二個辦法是「事后補救」贴硫,我們明確知道會存在循環(huán)引用,但是我們在合理的位置主動斷開環(huán)中的一個引用伊者,使得對象得以回收英遭。

五. 即時不互相持有,也需要weakSelf和strongSelf情況

// 設置 網絡 請求
- (void)setupNetworkRequest {
     [MBProgressHUD showLoading:nil toView:self.view];
    __weak typeof(self) weakSelf = self;
    [MSLogisticsListViewModel loadLogisticsInfoWithOrderNo:self.orderNo completion:^(LERequestResultType type, NSArray *dataArray) {
        __strong typeof(self) strongSelf = weakSelf;
        if (strongSelf) {
        
            [MBProgressHUD hideForView:strongSelf.view animated:YES];
        
            if (type == LERequestResultTypeSuccess || type == LERequestResultTypeNoMore || dataArray.count > 0) {
                strongSelf.logisticsDiverArray = dataArray;
                [strongSelf.tableView hideBlankPage];
                [strongSelf.tableView reloadData];
                return ;
            }
        
            if (type == LERequestResultTypeNoNetWork) {
                [strongSelf.tableView showBlankPageWithImage:networkTipsImage
                                                            tips:networkBrokenTips];
                return ;
            }
        
            if (type == LERequestResultTypeServerError) {
                [strongSelf.tableView showBlankPageWithImage:serverBusyTipsImage
                                                            tips:serverErrorTips];
                return;
            }
        
            if (dataArray.count == 0) {
                [strongSelf.tableView showBlankPageWithImage:logisticsTipsImage
                                                            tips:logisticsNullDataTips];
                return ;
            }
        }
    }];
}

比如這個由MSLogisticsListViewModel發(fā)起的網絡請求亦渗,因為MSLogisticsListViewModel持有block挖诸,block持有self,但是self并不持有MSLogisticsListViewModel,因此這里并不存在循環(huán)引用。那為什么也要用weakSelfstrongSelf.

因為當網絡請求回來后法精,這里可能會進行怎大量的數(shù)據(jù)處理和邏輯處理多律,如果數(shù)據(jù)處理和邏輯處理相對復雜,處理過程中可能會存在循環(huán)引用搂蜓,如果不用weakSelfstrongSelf狼荞,那么只有等到block里面的內容執(zhí)行完畢之后,才會調用dealloc方法帮碰,如果只是block內部的處理過程相味,也存在循環(huán)引用,就會導致內存泄漏殉挽。

而如果使用了weakSelfstrongSelf丰涉,那么當該視圖銷毀的時候,如果block還沒有回調斯碌,就會直接先調用dealloc方法一死,再走block,不過這是weakSelfnil傻唾,這就能保證比如控制器viewController進行pop之后投慈,立即釋放。

沒有用weakSelfstrongSelf:

- (void)dealloc {
    NSLog(@"dealloc method");
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"延遲 回調");
        [self.tableView reloadData];
        NSLog(@"tableView 刷新");
    });
}

pop viewController 打印結果:

 FJChatMessageViewDemo[37477:780816] 延遲 回調
 FJChatMessageViewDemo[37477:780816] tableView 刷新
 FJChatMessageViewDemo[37477:780816] dealloc method

使用weakSelfstrongSelf:

- (void)dealloc {
    NSLog(@"dealloc method");
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    __weak typeof(self) weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"延遲 回調");
        __strong typeof(self) strongSelf = weakSelf;
        if (strongSelf) {
            [weakSelf.tableView reloadData];
            NSLog(@"tableView 刷新");
        }
    });
}

pop viewController 打印結果:

 FJChatMessageViewDemo[37740:787636] dealloc method
 FJChatMessageViewDemo[37740:787636] 延遲 回調

六.最后:

送上一張喜歡的圖片:

風.jpg

如果覺得不錯冠骄,麻煩給個喜歡或star,如果有問題請及時反饋伪煤,謝謝!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末猴抹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子锁荔,更是在濱河造成了極大的恐慌蟀给,老刑警劉巖蝙砌,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異跋理,居然都是意外死亡择克,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門前普,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肚邢,“玉大人,你說我怎么就攤上這事拭卿÷夂” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵峻厚,是天一觀的道長响蕴。 經常有香客問我,道長惠桃,這世上最難降的妖魔是什么浦夷? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮辜王,結果婚禮上劈狐,老公的妹妹穿的比我還像新娘。我一直安慰自己呐馆,他們只是感情好肥缔,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著摹恰,像睡著了一般辫继。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上俗慈,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天姑宽,我揣著相機與錄音,去河邊找鬼闺阱。 笑死炮车,一個胖子當著我的面吹牛酣溃,可吹牛的內容都是我干的瘦穆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼赊豌,長吁一口氣:“原來是場噩夢啊……” “哼扛或!你這毒婦竟也來了碘饼?” 一聲冷哼從身側響起熙兔,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤悲伶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后住涉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體麸锉,經...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年舆声,在試婚紗的時候發(fā)現(xiàn)自己被綠了花沉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡媳握,死狀恐怖碱屁,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情毙芜,我是刑警寧澤忽媒,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站腋粥,受9級特大地震影響晦雨,放射性物質發(fā)生泄漏。R本人自食惡果不足惜隘冲,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一闹瞧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧展辞,春花似錦奥邮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至覆旱,卻和暖如春蘸朋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扣唱。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工藕坯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人噪沙。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓炼彪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親正歼。 傳聞我的和親對象是個殘疾皇子辐马,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內容