最近在作平板的過程當中,發現了一些很不規範的代碼。偶然修復支付bug的時候,看到其餘項目代碼,使用通知的地方沒有移除,我覺得我這個模塊的支付閃退是由於他通知沒有移除的緣故。而在debug和看了具體的代碼的時候才發現和這裏沒有關係。在我印象中,曾經由於沒有移除通知而遇到閃退的問題。因此讓我很意外,因而寫了個demo研究了下,同時來說下NSNotificationCenter
使用的正確姿式。多線程
對於這個不必多說,就是一個消息通知機制,相似廣播。觀察者只須要向消息中心註冊感興趣的東西,當有地方發出這個消息的時候,通知中心會發送給註冊這個消息的對象。這樣也起到了多個對象之間解耦的做用。蘋果給咱們封裝了這個NSNotificationCenter
,讓咱們能夠很方便的進行通知的註冊和移除。然而,有些人的姿式仍是有點小問題的,下面就看看正確的姿式吧!app
remove
只要往NSNotificationCenter
註冊了,就必須有remove
的存在,這點是你們共識的。可是你們在使用的時候發現,在UIViewController
中addObserver
後沒有移除,好像也沒有掛!我想不少人可能和我有同樣的疑問,是否是由於使用了ARC?在你對象銷燬的時候自動置爲nil
了呢?或者蘋果在實現這個類的時候用了什麼神奇的方式呢?下面咱們就一步步來探究下。async
首先,向NSNotificationCenter
中addObserver
後,並無對這個對象進行引用計數加1操做,因此它只是保存了地址。爲了驗證這個操做,咱們來作下代碼的測試。post
一個測試類,用來註冊通知:測試
@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中建立這個對象後,而後銷燬,最後發送這個通知:ui
- (void)viewDidLoad { [super viewDidLoad]; MRCObject *obj = [[MRCObject alloc] init]; [obj release]; [[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil]; }
在進入這個vc後,咱們發現掛了。。而打印出的信息是:spa
2015-01-19 22:49:06.655 測試[1158:286268] *** -[MRCObject test]: message sent to deallocated instance 0x17000e5b0
咱們能夠發現,向野指針對象發送了消息,因此掛掉了。從這點來看,蘋果實現也基本差很少是這樣的,只保存了個對象的地址,並無在銷燬的時候置爲nil
。線程
這點就能夠證實,addObserver
後,必需要有remove
操做。debug
如今咱們在UIViewController
中註冊通知,不移除,看看會不會掛掉。指針
- (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil]; }
首先用navigationController
進入到這個頁面,而後pop
出去。最後點擊發送通知的按鈕事件:
- (void)didButtonClicked:(id)sender { [[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil]; }
不管你怎麼點擊這個按鈕,他就是不掛!這下,是否是很鬱悶了?咱們能夠找找看,你代碼裏面沒有remove
操做,可是NSNotificationCenter
那邊已經移除了,否則確定會出現上面野指針的問題。看來看去,也只能說明是UIViewController
本身銷燬的時候幫咱們暗地裏移除了。
那咱們如何證實呢?因爲咱們看不到源碼,因此也不知道有沒有調用。這個時候,咱們能夠從這個通知中心下手!!!怎麼下手呢?我只要證實UIViewController
在銷燬的時候調用了remove
方法,就能夠證實咱們的猜測是對的了!這個時候,就須要用到咱們強大的類別這個特性了。咱們爲NSNotificationCenter
添加個類別,重寫他的- (void)removeObserver:(id)observer
方法:
- (void)removeObserver:(id)observer { NSLog(@"====%@ remove===", [observer class]); }
這樣在咱們VC中導入這個類別,而後pop
出來,看看發生了什麼!
2015-01-19 22:59:00.580 測試[1181:288728] ====TestViewController remove===
怎麼樣?是否是能夠證實系統的UIViewController
在銷燬的時候調用了這個方法。(不建議你們在開發的時候用類別的方式覆蓋原有的方法,因爲類別方法具備更高的優先權,因此有可能影響到其餘地方。這裏只是調試用)。
以上也提醒咱們,在你不是銷燬的時候,千萬不要直接調用[[NSNotificationCenter defaultCenter] removeObserver:self];
這個方法,由於你有可能移除了系統註冊的通知。
addObserver
在咱們開發中,咱們常常能夠看到這樣的代碼:
- (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]; }
就是在頁面出現的時候註冊通知,頁面消失時移除通知。你這邊可要注意了,必定要成雙成對出現,若是你只在viewWillAppear 中 addObserver
沒有在viewWillDisappear 中 removeObserver
那麼當消息發生的時候,你的方法會被調用屢次,這點必須牢記在心。
首先看下蘋果的官方說明:
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
消息的接受線程是基於發送消息的線程的。也就是同步的,所以,有時候,你發送的消息可能不在主線程,而你們都知道操做UI必須在主線程,否則會出現不響應的狀況。因此,在你收到消息通知的時候,注意選擇你要執行的線程。下面看個示例代碼
//接受消息通知的回調 - (void)test { if ([[NSThread currentThread] isMainThread]) { NSLog(@"main"); } else { NSLog(@"not main"); } dispatch_async(dispatch_get_main_queue(), ^{ //do your UI }); } //發送消息的線程 - (void)sendNotification { dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(defaultQueue, ^{ [[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil]; }); }
通知日常使用的知識點差很少就這麼多。但願對你們有幫助。最後,代碼必定要養成良好的習慣,該移除的仍是要移除。