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

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

@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)建兩個(gè)SecondViewController對(duì)象,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)時(shí)看到這個(gè)運(yùn)行結(jié)果,便果斷的判斷了是循環(huán)引用導(dǎo)致的問題,但卻不知道為什么。后來(lái)在學(xué)習(xí)了AFN的循環(huán)引用問題處理方式之后,突然想到,NSNotificationCenter和NSURLSession可能類似,是由于NSNotificationCenter沒有釋放,一直還持有block,所以導(dǎo)致VC1沒有釋放。用圖來(lái)表示:

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

到底是不是這個(gè)原因呢,我們從頭入手

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

NSNotification.h

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

@interface SecondViewController ()
@property (nonatomic, strong) id observer; //持有注冊(cè)通知后返回的對(duì)象
@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后返回的對(duì)象是一個(gè)__NSObserver類型,類中存儲(chǔ)著通知的一些相關(guān)信息:

  • observer對(duì)象中存儲(chǔ)的nc,即[NSNotificationCenter defaultCenter]
  • observer對(duì)象中存儲(chǔ)的block,即addObserver時(shí)傳入的block

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

__NSObserver<--> NSNotificationCenter<-->self

圖解:

  1. 調(diào)用[[NSNotificationCenter defaultCenter] addObserverForName:xxx之后,創(chuàng)建__NSObserver對(duì)象,并賦值:
__NSObserver *observer = [__NSObserver alloc] init];
observer->nc = [NSNotificationCenter defaultCenter];  //見圖中①
observer->block = myBlock;  //見圖中②
  1. NSNotificationCenter保存observer對(duì)象,用于通知分發(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)的過程。

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

那么,怎樣才是正確使用NSNotificationCenter的姿勢(shì)呢?

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

正確使用NSNotificationCenter的姿勢(shì)

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容