NSNotification引起的內(nèi)存泄漏和循環(huán)引用

在上一篇《OC循環(huán)引用》的文章中晦溪,介紹了NSNotification會導(dǎo)致循環(huán)引用,我們先來看一下那個例子挣跋。

@implementation SecondViewController
- (void)addObserver {
     [[NSNotificationCenter defaultCenter] addObserverForName:@"noticycle" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"%s, %@", __FUNCTION__, self);
    }];
}
- (void)postNotification {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"noticycle" object:nil];
    NSLog(@"%s, %@", __FUNCTION__, self);
}
@end

//調(diào)用代碼
@implementation FirstViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //創(chuàng)建兩個SecondViewController對象三圆,VC1做觀察者,VC2做通知發(fā)送者
    SecondViewController *VC1 = [[SecondViewController alloc] init];
    VC1.title = @"VC1";
    [VC1 addObserver];
    
    SecondViewController *VC2 = [[SecondViewController alloc] init];
    VC2.title = @"VC2";
    [VC2 postNotification];    
}
@end

運(yùn)行結(jié)果:

2017-09-06 12:31:17.710 RetainCycleDemo[58071:30501179] -[SecondViewController addObserver]_block_invoke, VC1
2017-09-06 12:31:17.710 RetainCycleDemo[58071:30501179] -[SecondViewController postNotification], VC2
2017-09-06 12:31:17.712 RetainCycleDemo[58071:30501179] -[SecondViewController dealloc], VC2

當(dāng)時看到這個運(yùn)行結(jié)果避咆,便果斷的判斷了是循環(huán)引用導(dǎo)致的問題舟肉,但卻不知道為什么。后來在學(xué)習(xí)了AFN的循環(huán)引用問題處理方式之后查库,突然想到路媚,NSNotificationCenter和NSURLSession可能類似,是由于NSNotificationCenter沒有釋放樊销,一直還持有block整慎,所以導(dǎo)致VC1沒有釋放。用圖來表示:

NSNotificationCenter的block中使用self围苫,導(dǎo)致self不銷毀

到底是不是這個原因呢裤园,我們從頭入手

查看- (id<NSObject>)addObserverForName:(NSNotificationName)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;方法的頭文件中的說明:

NSNotification.h

文檔中指出,返回值被系統(tǒng)強(qiáng)引用,并且調(diào)用者需要持有該對象以用來移除通知政勃。于是背稼,我們修改一下代碼宫纬,如下:

@interface SecondViewController ()
@property (nonatomic, strong) id observer; //持有注冊通知后返回的對象
@end

@implementation SecondViewController

- (void)addObserver {
    void(^myBlock)(NSNotification * _Nonnull note) = ^(NSNotification * _Nonnull note) {
        NSLog(@"%s, %@", __FUNCTION__, self.title);
    };
    self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"wangb" object:nil queue:[NSOperationQueue mainQueue] usingBlock:myBlock];
    NSLog(@"myBlock:%@", myBlock);  //打印myBlock的地址
}

- (void)postNotification {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"wangb" object:nil];
    NSLog(@"%s, %@", __FUNCTION__, self.title);
}

- (void)removeObserver {
    [[NSNotificationCenter defaultCenter] removeObserver:self.observer name:@"wangb" object:nil];
    self.observer = nil;
}

- (void)dealloc {
    NSLog(@"%s, %@", __FUNCTION__, self.title);
}
@end

//調(diào)用代碼
    SecondViewController *VC1 = [[SecondViewController alloc] init];
    VC1.title = @"VC1";
    [VC1 addObserver];
    
    SecondViewController *VC2 = [[SecondViewController alloc] init];
    VC2.title = @"VC2";
    [VC2 postNotification];

    [VC1 removeObserver];

運(yùn)行如下:

通過調(diào)試發(fā)現(xiàn)强法,addObserver后返回的對象是一個__NSObserver類型万俗,類中存儲著通知的一些相關(guān)信息:

  • observer對象中存儲的nc,即[NSNotificationCenter defaultCenter]
  • observer對象中存儲的block饮怯,即addObserver時傳入的block

這個__NSObserver特別像AFN里面的AFURLSessionManagerTaskDelegate闰歪,通過__NSObserver管理通知信息及回調(diào)接口。NSNotificationCenter中存儲observer蓖墅。所以库倘,從調(diào)用addObserver,到斷點(diǎn)的位置论矾,運(yùn)行流程及對象之間的關(guān)系猜測如下:

__NSObserver<--> NSNotificationCenter<-->self

圖解:

  1. 調(diào)用[[NSNotificationCenter defaultCenter] addObserverForName:xxx之后教翩,創(chuàng)建__NSObserver對象,并賦值:
__NSObserver *observer = [__NSObserver alloc] init];
observer->nc = [NSNotificationCenter defaultCenter];  //見圖中①
observer->block = myBlock;  //見圖中②
  1. NSNotificationCenter保存observer對象贪壳,用于通知分發(fā)饱亿,見圖中③
  2. NSNotificationCenter把observer返回,被self持有闰靴,見圖中④
  3. 當(dāng)postNotification后彪笼,NSNotificationCenter通過observer找到相關(guān)信息,通過block回調(diào)蚂且,block中調(diào)用self配猫。
  4. 到此為止,表示了運(yùn)行到斷點(diǎn)的過程杏死。

下面泵肄,再回過頭來看最開始的例子,self在沒有持有NSNotificationCenter的情況下淑翼,只是在block中單方面調(diào)用了self凡伊,就導(dǎo)致了VC1不能釋放,原因是由于observer單方面一直強(qiáng)引用VC1且observer沒有釋放窒舟,才導(dǎo)致的VC1沒有釋放系忙。

那么,怎樣才是正確使用NSNotificationCenter的姿勢呢惠豺?

方法一:切斷上圖中的⑤和③:

正確使用NSNotificationCenter的姿勢

方法二:如果在block中不是用weakSelf银还,那么必須要先銷毀observer,才能解除對self的強(qiáng)引用洁墙。需要注意的是蛹疯,這種情況下,不要嘗試在dealloc方法中做任何操作热监,因?yàn)樵趏bserver解除對self的強(qiáng)引用前捺弦,self是釋放不了的,所以都不會調(diào)用到dealloc方法。

方法二中列吼,如果observer屬性用weak修飾幽崩,則在removeObserver中可以不用寫self.observer = nil;
在開發(fā)中,可以按照方法一來就可以了寞钥,最常規(guī)慌申,最靠譜!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末理郑,一起剝皮案震驚了整個濱河市蹄溉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌您炉,老刑警劉巖柒爵,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赚爵,居然都是意外死亡棉胀,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門囱晴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膏蚓,“玉大人瓢谢,你說我怎么就攤上這事畸写。” “怎么了氓扛?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵枯芬,是天一觀的道長。 經(jīng)常有香客問我采郎,道長千所,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任蒜埋,我火速辦了婚禮淫痰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘整份。我一直安慰自己待错,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布烈评。 她就那樣靜靜地躺著火俄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪讲冠。 梳的紋絲不亂的頭發(fā)上瓜客,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼谱仪。 笑死玻熙,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芽卿。 我是一名探鬼主播揭芍,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼卸例!你這毒婦竟也來了称杨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤筷转,失蹤者是張志新(化名)和其女友劉穎姑原,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呜舒,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锭汛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了袭蝗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唤殴。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖到腥,靈堂內(nèi)的尸體忽然破棺而出朵逝,到底是詐尸還是另有隱情,我是刑警寧澤乡范,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布配名,位于F島的核電站,受9級特大地震影響晋辆,放射性物質(zhì)發(fā)生泄漏渠脉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一瓶佳、第九天 我趴在偏房一處隱蔽的房頂上張望芋膘。 院中可真熱鬧,春花似錦霸饲、人聲如沸为朋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潜腻。三九已至,卻和暖如春器仗,著一層夾襖步出監(jiān)牢的瞬間融涣,已是汗流浹背童番。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留威鹿,地道東北人剃斧。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像忽你,于是被迫代替她去往敵國和親幼东。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容