NSNotificationCenter

NSNotificationCenter

首先看兩段 Apple Doc 上的話:html

  1. NSNotificationCenter

In a multithreaded application, notifications are always delivered in the thread in which the notification was posted, which may not be the same thread in which an observer registered itself.bash

  1. Delivering Notifications To Particular Threads

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.app

錯誤的理解

最初我看到這兩段以後以爲 Notification Center 是這樣工做的:函數

  1. 線程 A 經過調用 Notification Center 上的 - addObserver:selector:name:object: 註冊一個 Observer
  2. 線程 B 經過調用 Notification Center 上的 - postNotification: 來向 Observers 們發出通知
  3. Notification Center 發現線程 B 上以前並無註冊過相應的 Observer,因而什麼都沒作

不理解說的什麼請忽略,畢竟 Notification Center 實際不是這樣實現的oop

正確的方式

回想在『錯誤的理解』中的方式,若是要實現那樣的方式,Notification Center 勢必須要維護 Threads 和在它們之上註冊的 Observers 的映射表。這樣實現也是挺麻煩的,而實際的工做方式是這樣:post

  1. 線程 A 經過調用 Notification Center 上的 - addObserver:selector:name:object: 註冊一個 Observer。能夠注意下這三個參數,它們分別是 函數運行的上下文須要運行的函數函數被調用時傳遞的實參,那麼 Notification Center 內部實際上就是存儲了這個調用結構體(沒有源碼,可是大概就是這個意思),而後將這些個調用結構體根據 Notification Name 組織到一個 Set 中。
  2. 線程 B 經過調用 Notification Center 上的 - postNotification: 來向 Observers 們發出通知。因而 Notification Center 在內部維護的調用結構體 Set 中找到了相關的 Observers,而後在 B 線程上面分別的以它們被添加時的 函數運行上下文須要運行的函數函數被傳遞時的實參 來逐個調用它們。
  3. 因而你會發現,你在 A 線程中註冊的 Observers,可是因爲你在 B 線程中 post notification,最後那些 Observers 都在 B 線程中被運行了。

爲了避免再理解錯 😂,須要用代碼驗證下,先看看命令行的:測試

#import <Foundation/Foundation.h>
#import <pthread.h>

#define CURRENT_THREAD_ID (pthread_mach_thread_np(pthread_self()))

static NSString* const CrossThreadNotification = @"CrossThreadNotification";

@interface SubThread : NSThread

@end

@implementation SubThread

- (void)main
{
    NSLog(@"Sub thead is running: %u", CURRENT_THREAD_ID);

    // 往 RunLoop 中添加一個 port 這樣 RunLoop 纔不會由於沒有 source 而退出
    NSPort* port = [[NSPort alloc] init];
    [[NSRunLoop currentRunLoop] addPort:port forMode:NSDefaultRunLoopMode];

    // 在子線程中註冊 observer
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(receievedNotification)
                                                 name:CrossThreadNotification
                                               object:nil];

    [[NSRunLoop currentRunLoop] run];
}

- (void)receievedNotification
{
    NSLog(@"%s %u", __func__, CURRENT_THREAD_ID);
}

@end

int main(int argc, const char* argv[])
{
    @autoreleasepool
    {
        NSLog(@"press 'q' to exit...");

        SubThread* sub = [[SubThread alloc] init];
        [sub start];

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC),
            dispatch_get_global_queue(0, 0), ^{
                // 咱們添加 observer 的動做發生在 SubThread 的 `-main` 中,等 0.1 秒讓 SubThread 運行起來
                NSLog(@"try to notify CrossThreadNotification in %u", CURRENT_THREAD_ID);
                [[NSNotificationCenter defaultCenter] postNotificationName:CrossThreadNotification object:nil];
            });

        int c = getchar();
        while (c != 'q') {
            c = getchar();
        }
    }
    return 0;
}
複製代碼

運行後的結果:ui

press 'q' to exit...
Sub thead is running: 6147
try to notify CrossThreadNotification in 4099
-[SubThread receievedNotification] 4099
複製代碼

能夠看到 post 和執行都發生在 4099,說明了 observer 的執行發生在 post 所在的線程。spa

再看一個 iOS 中的,大同小異,不過咱們測試了兩個,一個是 System Notification,一個 Custom Notification:命令行

#import <pthread.h>

#define CURRENT_THREAD_ID (pthread_mach_thread_np(pthread_self()))

static NSString* const CrossThreadNotification = @"CrossThreadNotification";

@interface SubThread : NSThread

@end

@implementation SubThread

- (void)main
{
    NSLog(@"Sub thead is running: %u", CURRENT_THREAD_ID);

    NSPort* port = [[NSPort alloc] init];
    [[NSRunLoop currentRunLoop] addPort:port forMode:NSDefaultRunLoopMode];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(receievedNotification)
                                                 name:UIApplicationDidReceiveMemoryWarningNotification
                                               object:nil];
    
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(receievedNotification)
                                                 name:CrossThreadNotification
                                               object:nil];

    [[NSRunLoop currentRunLoop] run];
}

- (void)receievedNotification
{
    NSLog(@"%s %u", __func__, CURRENT_THREAD_ID);
}

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
    NSLog(@"Main thread ID: %u", CURRENT_THREAD_ID);

    SubThread* sub = [[SubThread alloc] init];
    [sub start];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC),
        dispatch_get_global_queue(0, 0), ^{
            // 5 秒後在另外一個線程中 post notification
            NSLog(@"try to notify CrossThreadNotification in %u", CURRENT_THREAD_ID);
            [[NSNotificationCenter defaultCenter] postNotificationName:CrossThreadNotification object:nil];
        });

    return YES;
}
複製代碼

這是運行的結果:

Main thread ID: 1803
Sub thead is running: 19715
try to notify CrossThreadNotification in 9475
-[SubThread receievedNotification] 9475
Received memory warning.
-[SubThread receievedNotification] 1803
複製代碼

首先兩個 9475 得出的結果和在命令行中的結果一致:observer 的執行發生在 post 所在的線程。另外還注意到,提供通知 UIApplicationDidReceiveMemoryWarningNotification 是在主線程上 post 的(兩個 1803),因而咱們的 observer 在主線程上執行了。

可是我尚未在 Apple Doc 中找到說全部的系統調用都在主線程上被 post,可是至少以 UI 開頭的系統調用會是在主線程上被調用的,好比上面的 UIApplicationDidReceiveMemoryWarningNotification

Happy coding!

相關文章
相關標籤/搜索