ios 的 notification 在多線程的狀況下,線程的管理很是很差控制。這個怎麼理解呢?html
按照官方文檔的說法就是,無論你在哪一個線程註冊了 observer,notification 在哪一個線程 post,那麼它就將在哪一個線程接收,這個意思用代碼表示,效果以下:ios
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"current thread = %@", [NSThread currentThread]); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:POST_NOTIFICATION object:nil]; } - (void)viewDidAppear:(BOOL)animated { [self postNotificationInBackground]; } - (void)postNotificationInBackground { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:POST_NOTIFICATION object:nil userInfo:nil]; }); } - (void)handleNotification:(NSNotification *)notification { NSLog(@"current thread = %@", [NSThread currentThread]); }
輸出以下:git
2016-07-02 11:20:56.683 Test[31784:3602420] current thread = <NSThread: 0x7f8548405250>{number = 1, name = main} 2016-07-02 11:20:56.684 Test[31784:3602420] viewWillAppear: ViewController 2016-07-02 11:20:56.689 Test[31784:3602469] current thread = <NSThread: 0x7f854845b790>{number = 2, name = (null)}
也就是說,儘管我在主線程註冊了 observer,可是因爲我在子線程 post 了消息,那麼 handleNotification 響應函數也會在子線程處理。這樣一來就會給咱們帶來困擾,由於 notification 的響應函數執行線程將變得不肯定,並且不少操做如 UI 操做,咱們是須要在主線程進行的。github
怎麼解決這個問題呢?api
一個很土的方法就在在 handleNotification 裏面,強制切換線程,如:數組
- (void)handleNotification:(NSNotification *)notification { dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"current thread = %@", [NSThread currentThread]); }); }
每個響應函數都強制切換線程。這樣帶來的問題就是每一處理代碼你都得這樣作,對於開發者而言負擔太大,顯然是下下策。多線程
其實解決思路和上面的差很少,不過實現的方式更優雅一點,這個方案在 apple 的官方文檔中有詳細介紹,它的思路翻譯過來就是:重定向通知的一種的實現思路是使用一個通知隊列(注意,不是 NSNotificationQueue 對象,而是一個數組)去記錄全部的被拋向非預期線程裏面的通知,而後將它們重定向到預期線程。這種方案使咱們仍然是像日常同樣去註冊一個通知的觀察者,當接收到 Notification 的時候,先判斷 post 出來的這個 Notification 的線程是否是咱們所指望的線程,若是不是,則將這個 Notification 存儲到咱們自定義的隊列中,併發送一個信號( signal )到指望的線程中,來告訴這個線程須要處理一個 Notification 。指定的線程在收到信號後,將 Notification 從隊列中移除,並進行處理。併發
/* Threaded notification support. */ @property (nonatomic) NSMutableArray *notifications; // 通知隊列 @property (nonatomic) NSThread *notificationThread; // 預想的處理通知的線程 @property (nonatomic) NSLock *notificationLock; // 用於對通知隊列加鎖的鎖對象,避免線程衝突 @property (nonatomic) NSMachPort *notificationPort; // 用於向預想的處理線程發送信號的通訊端口 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"current thread = %@", [NSThread currentThread]); [self setUpThreadingSupport]; // 往當前線程的run loop添加端口源 // 當Mach消息到達而接收線程的run loop沒有運行時,則內核會保存這條消息,直到下一次進入run loop [[NSRunLoop currentRunLoop] addPort:self.notificationPort forMode:(__bridge NSString *)kCFRunLoopCommonModes]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:POST_NOTIFICATION object:nil]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:POST_NOTIFICATION object:nil userInfo:nil]; }); } - (void) setUpThreadingSupport { if (self.notifications) { return; } self.notifications = [[NSMutableArray alloc] init]; self.notificationLock = [[NSLock alloc] init]; self.notificationThread = [NSThread currentThread]; self.notificationPort = [[NSMachPort alloc] init]; [self.notificationPort setDelegate:self]; [[NSRunLoop currentRunLoop] addPort:self.notificationPort forMode:(__bridge NSString*)kCFRunLoopCommonModes]; } - (void)handleMachMessage:(void *)msg { [self.notificationLock lock]; while ([self.notifications count]) { NSNotification *notification = [self.notifications objectAtIndex:0]; [self.notifications removeObjectAtIndex:0]; [self.notificationLock unlock]; [self processNotification:notification]; [self.notificationLock lock]; }; [self.notificationLock unlock]; } - (void)processNotification:(NSNotification *)notification { if ([NSThread currentThread] != _notificationThread) { // Forward the notification to the correct thread. [self.notificationLock lock]; [self.notifications addObject:notification]; [self.notificationLock unlock]; [self.notificationPort sendBeforeDate:[NSDate date] components:nil from:nil reserved:0]; } else { // Process the notification here; NSLog(@"current thread = %@", [NSThread currentThread]); NSLog(@"process notification"); } } }
可是這種方案有明顯額缺陷,官方文檔也對其進行了說明,歸結起來有兩點:app
全部的通知的處理都要通過 processNotification 函數進行處理。async
全部的接聽對象都要提供相應的 NSMachPort 對象,進行消息轉發。
正是因爲存在這樣的缺陷,所以官方文檔並不建議直接這樣使用,而是鼓勵開發者去繼承NSNoticationCenter 或者本身去提供一個單獨的類進行線程的維護。
爲了順應語法的變化,apple 從 ios4 以後提供了帶有 block 的 NSNotification。使用方式以下:
- (id<NSObject>)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block
這裏說明幾點
觀察者就是當前對象
queue 定義了 block 執行的線程,nil 則表示 block 的執行線程和發通知在同一個線程
block 就是相應通知的處理函數
這個 API 已經可以讓咱們方便的控制通知的線程切換。可是,這裏有個問題須要注意。就是其 remove 操做。
首先回憶一下咱們原來的 NSNotification 的 remove 方式,見以下代碼:
- (void)removeObservers { [[NSNotificationCenter defaultCenter] removeObserver:self name:POST_NOTIFICATION object:nil]; }
須要指定 observer 以及 name。可是帶 block 方式的 remove 便不能像上面這樣處理了。其方式以下:
- (void)removeObservers { if(_observer){ [[NSNotificationCenter defaultCenter] removeObserver:_observer]; } }
其中 _observer 是 addObserverForName 方式的 api 返回觀察者對象。這也就意味着,你須要爲每個觀察者記錄一個成員對象,而後在 remove 的時候依次刪除。試想一下,你若是須要 10 個觀察者,則須要記錄 10 個成員對象,這個想一想就是很麻煩,並且它還不可以方便的指定 observer 。所以,理想的作法就是本身再作一層封裝,將這些細節封裝起來。
git 上有一個想要解決上述問題的開源代碼,其使用方式以下:
+ (void)observeName:(NSString *)name owner:(id)owner dispatchQueue:(dispatch_queue_t)dispatchQueue block:(LRNotificationObserverBlock)block;
它可以方便的控制線程切換,並且它還能作到 owner dealloc 的時候,自動 remove observer。好比咱們不少時候在 viewDidLoad 的時候addObserver,而後還須要重載 dealloc,在裏面調用 removeObserver,這個開源方案,幫咱們省去了再去dealloc 顯示 remove 的額外工做。可是若是你想顯式的調用 remove,就比較麻煩了(好比有時候,咱們在viewWillAppear 添加了 observer,須要在 viewWillDisAppear 移除 observer),它相似官方的解決方案,須要你用成員變量,將 observer 一個個保存下來,而後在 remove 的地方移除。
爲了解決上面的問題,所以決定從新寫一個 Notification 的管理類,GYNotificationCenter 想要達到的效果有兩個
可以方便的控制線程切換
可以方便的remove observer
- (void)addObserver:(nonnull id)observer name:(nonnull NSString *)aName dispatchQueue:(nullable dispatch_queue_t)disPatchQueue block:(nonnull GYNotificatioObserverBlock)block;
咱們提供了和官方 api 幾乎同樣的調用方法,支持傳入 dispatchQueue 實現線程切換控制,同時可以以 block 的方式處理消息響應,並且支持在 observer dealloc 的時候,自動調用 observer 的 remove 操做。同時還提供了和原生同樣的顯式調用 remove 的操做,方便收到調用 remove .
- (void)removerObserver:(nonnull id)observer name:(nonnull NSString *)anName object:(nullable id)anObject; - (void)removerObserver:(nonnull id)observer;
可以方便的手動調用 remove 操做。
GYNotificaionCenter 借鑑了官方的線程重定向 以及 LRNotificationObserver 的一些方案。在 addObserver 的時候,生成了一個和 observer 關聯的 GYNotificationOberverIdentifer 對象,這個對象記錄了傳入的 block 、name 的數據,而後對這個對象依據傳入的 name 註冊觀察者。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:anName object:object];
當收到通知的時候,在 handleNotification 裏面執行傳入的 block,回調的外面去。
- (void)handleNotification:(NSNotification *)notification { if (self.dispatchQueue) { dispatch_async(self.dispatchQueue, ^{ if (self.block) { self.block(notification); } }); } else { self.block(notification); } }
GYNotificationOberverIdentifer 對象放入 GYNotificationOberverIdentifersContainer 對象中進行統一管理。
- (void)addNotificationOberverIdentifer:(GYNotificationOberverIdentifer *)identifier { NSAssert(identifier,@"identifier is nil"); if (identifier) { NotificationPerformLocked(^{ [self modifyContainer:^(NSMutableDictionary *notificationOberverIdentifersDic) { //不重複add observer if (![notificationOberverIdentifersDic objectForKey:identifier.name]) { [notificationOberverIdentifersDic setObject:identifier forKey:identifier.name]; } }]; }); } }
這個對象也和 observer 關聯。因爲其和 observer 是關聯的,所以當 observer 釋放的時候,GYNotificationOberverIdentifer 也會釋放,所以,也就能在 GYNotificationOberverIdentifer 的 dealloc 裏面調用 remove 操做移除通知註冊從而實現自動 remove。
同時因爲 GYNotificationOberverIdentifersContainer 裏面保留了全部的 Identifer 對象,所以也就可以方便的根據 name 進行 remove 了。
- (void)removeObserverWithName:(NSString *)name { if (name) { NotificationPerformLocked(^{ [self modifyContainer:^(NSMutableDictionary *notificationOberverIdentifersDic) { if ([notificationOberverIdentifersDic objectForKey:name]) { GYNotificationOberverIdentifer *identifier = (GYNotificationOberverIdentifer *)[notificationOberverIdentifersDic objectForKey:name]; [identifier stopObserver]; [notificationOberverIdentifersDic removeObjectForKey:name]; } }]; }); } }