你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> NSNotification線程管理以及自動注銷開源方案

NSNotification線程管理以及自動注銷開源方案

編輯:IOS開發基礎

notification_center_notifcations_view_iphone_6_hero.jpg

背景

iOS的notification在多線程的情況下,線程的管理非常不好控制。這個怎麼理解呢?

按照官方文檔的說法就是,不管你在哪個線程注冊了 observer,notification 在哪個線程 post,那麼它就將在哪個線程接收,這個意思用代碼表示,效果如下:

- (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]);
}

輸出如下:

2016-07-02 11:20:56.683 Test[31784:3602420] current thread = {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 = {number = 2, name = (null)}

也就是說,盡管我在主線程注冊了 observer,但是由於我在子線程 post 了消息,那麼 handleNotification 響應函數也會在子線程處理。這樣一來就會給我們帶來困擾,因為 notification 的響應函數執行線程將變得不確定,而且很多操作如 UI 操作,我們是需要在主線程進行的。

解決方案

怎麼解決這個問題呢?

在響應函數處強制切線程

實現

一個很土的方法就在在 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");
    }
}
}

但是這種方案有明顯額缺陷,官方文檔也對其進行了說明,歸結起來有兩點:

  • 所有的通知的處理都要經過 processNotification 函數進行處理。

  • 所有的接聽對象都要提供相應的 NSMachPort 對象,進行消息轉發。

正是由於存在這樣的缺陷,因此官方文檔並不建議直接這樣使用,而是鼓勵開發者去繼承NSNoticationCenter 或者自己去提供一個單獨的類進行線程的維護。

block 方式的 NSNotification

為了順應語法的變化,apple 從 ios4 之後提供了帶有 block 的 NSNotification。使用方式如下:

- (id)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 。因此,理想的做法就是自己再做一層封裝,將這些細節封裝起來。

LRNotificationObserver

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 的地方移除。

GYNotificationCenter

  • 為了解決上面的問題,因此決定重新寫一個 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];
                }
            }];
        });
    }
}
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved