在上一篇《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沒有釋放。用圖來表示:
到底是不是這個原因呢裤园,我們從頭入手
查看- (id<NSObject>)addObserverForName:(NSNotificationName)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;
方法的頭文件中的說明:
文檔中指出,返回值被系統(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)系猜測如下:
圖解:
- 調(diào)用
[[NSNotificationCenter defaultCenter] addObserverForName:xxx
之后教翩,創(chuàng)建__NSObserver對象,并賦值:
__NSObserver *observer = [__NSObserver alloc] init];
observer->nc = [NSNotificationCenter defaultCenter]; //見圖中①
observer->block = myBlock; //見圖中②
- NSNotificationCenter保存observer對象贪壳,用于通知分發(fā)饱亿,見圖中③
- NSNotificationCenter把observer返回,被self持有闰靴,見圖中④
- 當(dāng)postNotification后彪笼,NSNotificationCenter通過observer找到相關(guān)信息,通過block回調(diào)蚂且,block中調(diào)用self配猫。
- 到此為止,表示了運(yùn)行到斷點(diǎn)的過程杏死。
下面泵肄,再回過頭來看最開始的例子,self在沒有持有NSNotificationCenter的情況下淑翼,只是在block中單方面調(diào)用了self凡伊,就導(dǎo)致了VC1不能釋放,原因是由于observer單方面一直強(qiáng)引用VC1且observer沒有釋放窒舟,才導(dǎo)致的VC1沒有釋放系忙。
那么,怎樣才是正確使用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ī)慌申,最靠譜!