最近在做平板的過程中侦铜,發(fā)現(xiàn)了一些很不規(guī)范的代碼院塞。偶然修復支付bug的時候,看到其他項目代碼,使用通知的地方?jīng)]有移除渣蜗,我以為我這個模塊的支付閃退是因為他通知沒有移除的緣故屠尊。而在debug和看了具體的代碼的時候才發(fā)現(xiàn)和這里沒有關系。在我印象中耕拷,曾經(jīng)因為沒有移除通知而遇到閃退的問題讼昆。所以讓我很意外,于是寫了個demo研究了下骚烧,同時來講下NSNotificationCenter
使用的正確姿勢浸赫。
NSNotificationCenter
對于這個沒必要多說,就是一個消息通知機制止潘,類似廣播掺炭。觀察者只需要向消息中心注冊感興趣的東西辫诅,當有地方發(fā)出這個消息的時候凭戴,通知中心會發(fā)送給注冊這個消息的對象。這樣也起到了多個對象之間解耦的作用炕矮。蘋果給我們封裝了這個NSNotificationCenter
么夫,讓我們可以很方便的進行通知的注冊和移除。然而肤视,有些人的姿勢還是有點小問題的档痪,下面就看看正確的姿勢吧!
正確姿勢之remove
只要往NSNotificationCenter
注冊了邢滑,就必須有remove
的存在腐螟,這點是大家共識的。但是大家在使用的時候發(fā)現(xiàn)困后,在UIViewController
中addObserver
后沒有移除乐纸,好像也沒有掛!我想很多人可能和我有一樣的疑問摇予,是不是因為使用了ARC汽绢?在你對象銷毀的時候自動置為nil
了呢?或者蘋果在實現(xiàn)這個類的時候用了什么神奇的方式呢侧戴?下面我們就一步步來探究下宁昭。
首先,向NSNotificationCenter
中addObserver
后酗宋,并沒有對這個對象進行引用計數(shù)加1操作积仗,所以它只是保存了地址。為了驗證這個操作蜕猫,我們來做下代碼的測試斥扛。
一個測試類,用來注冊通知:
@implementation MRCObject
- (id)init
{
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil];
}
return self;
}
- (void)test
{
NSLog(@"=================");
}
- (void)dealloc
{
[super dealloc];
}
@end
這個類很簡單,就是在初始化的時候稀颁,給他注冊一個通知芬失。但是在銷毀的時候不進行remove
操作。我們在VC中創(chuàng)建這個對象后匾灶,然后銷毀,最后發(fā)送這個通知:
- (void)viewDidLoad {
[super viewDidLoad];
MRCObject *obj = [[MRCObject alloc] init];
[obj release];
[[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
}
在進入這個vc后阶女,我們發(fā)現(xiàn)掛了。秃踩。而打印出的信息是:
2015-01-19 22:49:06.655 測試[1158:286268] *** -[MRCObject test]: message sent to deallocated instance 0x17000e5b0
我們可以發(fā)現(xiàn),向野指針對象發(fā)送了消息憔杨,所以掛掉了鸟赫。從這點來看消别,蘋果實現(xiàn)也基本差不多是這樣的,只保存了個對象的地址寻狂,并沒有在銷毀的時候置為nil
。
這點就可以證明,addObserver
后病袄,必須要有remove
操作塘慕。
現(xiàn)在我們在UIViewController
中注冊通知,不移除菜枷,看看會不會掛掉苍糠。
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil];
}
首先用navigationController
進入到這個頁面,然后pop
出去啤誊。最后點擊發(fā)送通知的按鈕事件:
- (void)didButtonClicked:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
}
無論你怎么點擊這個按鈕岳瞭,他就是不掛!這下蚊锹,是不是很郁悶了瞳筏?我們可以找找看,你代碼里面沒有remove
操作牡昆,但是NSNotificationCenter
那邊已經(jīng)移除了,不然肯定會出現(xiàn)上面野指針的問題。看來看去石窑,也只能說明是UIViewController
自己銷毀的時候幫我們暗地里移除了肯夏。
那我們?nèi)绾巫C明呢烛恤?由于我們看不到源碼苹熏,所以也不知道有沒有調(diào)用轨域。這個時候干发,我們可以從這個通知中心下手<叫!!怎么下手呢顺献?我只要證明UIViewController
在銷毀的時候調(diào)用了remove
方法嫁怀,就可以證明我們的猜想是對的了萝招!這個時候,就需要用到我們強大的類別這個特性了槐沼。我們?yōu)?code>NSNotificationCenter添加個類別捌治,重寫他的- (void)removeObserver:(id)observer
方法:
- (void)removeObserver:(id)observer
{
NSLog(@"====%@ remove===", [observer class]);
}
這樣在我們VC中導入這個類別,然后pop
出來肖油,看看發(fā)生了什么兼吓!
2015-01-19 22:59:00.580 測試[1181:288728] ====TestViewController remove===
怎么樣?是不是可以證明系統(tǒng)的UIViewController
在銷毀的時候調(diào)用了這個方法。(不建議大家在開發(fā)的時候用類別的方式覆蓋原有的方法,由于類別方法具有更高的優(yōu)先權(quán)暴拄,所以有可能影響到其他地方次和。這里只是調(diào)試用)。
以上也提醒我們那伐,在你不是銷毀的時候石蔗,千萬不要直接調(diào)用[[NSNotificationCenter defaultCenter] removeObserver:self];
這個方法养距,因為你有可能移除了系統(tǒng)注冊的通知日熬。
正確姿勢之注意重復addObserver
在我們開發(fā)中竖席,我們經(jīng)常可以看到這樣的代碼:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"test" object:nil];
}
就是在頁面出現(xiàn)的時候注冊通知束析,頁面消失時移除通知员寇。你這邊可要注意了第美,一定要成雙成對出現(xiàn),如果你只在viewWillAppear 中 addObserver
沒有在viewWillDisappear 中 removeObserver
那么當消息發(fā)生的時候扳缕,你的方法會被調(diào)用多次第献,這點必須牢記在心兔港。
正確姿勢之多線程通知
首先看下蘋果的官方說明:
Regular notification centers deliver notifications on the thread in which the notification was posted. Distributed notification centers deliver notifications on the main thread. At times, you may require notifications to be delivered on a particular thread that is determined by you instead of the notification center. For example, if an object running in a background thread is listening for notifications from the user interface, such as a window closing, you would like to receive the notifications in the background thread instead of the main thread. In these cases, you must capture the notifications as they are delivered on the default thread and redirect them to the appropriate thread.
意思很簡單衫樊,NSNotificationCenter
消息的接受線程是基于發(fā)送消息的線程的利花。也就是同步的,因此臀栈,有時候权薯,你發(fā)送的消息可能不在主線程,而大家都知道操作UI必須在主線程盟蚣,不然會出現(xiàn)不響應的情況。所以阐枣,在你收到消息通知的時候奄抽,注意選擇你要執(zhí)行的線程逞度。下面看個示例代碼
//接受消息通知的回調(diào)
- (void)test
{
if ([[NSThread currentThread] isMainThread]) {
NSLog(@"main");
} else {
NSLog(@"not main");
}
dispatch_async(dispatch_get_main_queue(), ^{
//do your UI
});
}
//發(fā)送消息的線程
- (void)sendNotification
{
dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(defaultQueue, ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
});
}
總結(jié)
通知平常使用的知識點差不多就這么多第晰。希望對大家有幫助。最后品抽,代碼一定要養(yǎng)成良好的習慣甜熔,該移除的還是要移除。