在上一篇《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)表示:

到底是不是這個(gè)原因呢,我們從頭入手
查看- (id<NSObject>)addObserverForName:(NSNotificationName)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;方法的頭文件中的說(shuō)明:

文檔中指出,返回值被系統(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è)如下:

圖解:
- 調(diào)用
[[NSNotificationCenter defaultCenter] addObserverForName:xxx之后,創(chuàng)建__NSObserver對(duì)象,并賦值:
__NSObserver *observer = [__NSObserver alloc] init];
observer->nc = [NSNotificationCenter defaultCenter]; //見圖中①
observer->block = myBlock; //見圖中②
- NSNotificationCenter保存observer對(duì)象,用于通知分發(fā),見圖中③
- NSNotificationCenter把observer返回,被self持有,見圖中④
- 當(dāng)postNotification后,NSNotificationCenter通過observer找到相關(guān)信息,通過block回調(diào),block中調(diào)用self。
- 到此為止,表示了運(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ì)呢?
方法一:切斷上圖中的⑤和③:

方法二:如果在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ī),最靠譜!