NSNotificationCenter
首先看兩段 Apple Doc 上的話:
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.
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.
錯(cuò)誤的理解
最初我看到這兩段之后覺得 Notification Center 是這樣工作的:
- 線程 A 通過調(diào)用 Notification Center 上的
- addObserver:selector:name:object:注冊(cè)一個(gè) Observer - 線程 B 通過調(diào)用 Notification Center 上的
- postNotification:來向 Observers 們發(fā)出通知 - Notification Center 發(fā)現(xiàn)線程 B 上之前并沒有注冊(cè)過相應(yīng)的 Observer,于是什么都沒做
不理解說的什么請(qǐng)忽略,畢竟 Notification Center 實(shí)際不是這樣實(shí)現(xiàn)的
正確的方式
回想在『錯(cuò)誤的理解』中的方式,如果要實(shí)現(xiàn)那樣的方式,Notification Center 勢(shì)必需要維護(hù) Threads 和在它們之上注冊(cè)的 Observers 的映射表。這樣實(shí)現(xiàn)也是挺麻煩的,而實(shí)際的工作方式是這樣:
- 線程 A 通過調(diào)用 Notification Center 上的
- addObserver:selector:name:object:注冊(cè)一個(gè) Observer。可以注意下這三個(gè)參數(shù),它們分別是函數(shù)運(yùn)行的上下文、需要運(yùn)行的函數(shù)、函數(shù)被調(diào)用時(shí)傳遞的實(shí)參,那么 Notification Center 內(nèi)部實(shí)際上就是存儲(chǔ)了這個(gè)調(diào)用結(jié)構(gòu)體(沒有源碼,但是大概就是這個(gè)意思),然后將這些個(gè)調(diào)用結(jié)構(gòu)體根據(jù) Notification Name 組織到一個(gè) Set 中。 - 線程 B 通過調(diào)用 Notification Center 上的
- postNotification:來向 Observers 們發(fā)出通知。于是 Notification Center 在內(nèi)部維護(hù)的調(diào)用結(jié)構(gòu)體 Set 中找到了相關(guān)的 Observers,然后在 B 線程上面分別的以它們被添加時(shí)的函數(shù)運(yùn)行上下文、需要運(yùn)行的函數(shù)、函數(shù)被傳遞時(shí)的實(shí)參來逐個(gè)調(diào)用它們。 - 于是你會(huì)發(fā)現(xiàn),你在 A 線程中注冊(cè)的 Observers,但是由于你在 B 線程中 post notification,最后那些 Observers 都在 B 線程中被運(yùn)行了。
為了不再理解錯(cuò) ??,需要用代碼驗(yàn)證下,先看看命令行的:
#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 中添加一個(gè) port 這樣 RunLoop 才不會(huì)因?yàn)闆]有 source 而退出
NSPort* port = [[NSPort alloc] init];
[[NSRunLoop currentRunLoop] addPort:port forMode:NSDefaultRunLoopMode];
// 在子線程中注冊(cè) 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 的動(dòng)作發(fā)生在 SubThread 的 `-main` 中,等 0.1 秒讓 SubThread 運(yùn)行起來
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;
}
運(yùn)行后的結(jié)果:
press 'q' to exit...
Sub thead is running: 6147
try to notify CrossThreadNotification in 4099
-[SubThread receievedNotification] 4099
可以看到 post 和執(zhí)行都發(fā)生在 4099,說明了 observer 的執(zhí)行發(fā)生在 post 所在的線程。
再看一個(gè) iOS 中的,大同小異,不過我們測(cè)試了兩個(gè),一個(gè)是 System Notification,一個(gè) 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 秒后在另一個(gè)線程中 post notification
NSLog(@"try to notify CrossThreadNotification in %u", CURRENT_THREAD_ID);
[[NSNotificationCenter defaultCenter] postNotificationName:CrossThreadNotification object:nil];
});
return YES;
}
這是運(yùn)行的結(jié)果:
Main thread ID: 1803
Sub thead is running: 19715
try to notify CrossThreadNotification in 9475
-[SubThread receievedNotification] 9475
Received memory warning.
-[SubThread receievedNotification] 1803
首先兩個(gè) 9475 得出的結(jié)果和在命令行中的結(jié)果一致:observer 的執(zhí)行發(fā)生在 post 所在的線程。另外還注意到,提供通知 UIApplicationDidReceiveMemoryWarningNotification 是在主線程上 post 的(兩個(gè) 1803),于是我們的 observer 在主線程上執(zhí)行了。
但是我還沒有在 Apple Doc 中找到說所有的系統(tǒng)調(diào)用都在主線程上被 post,但是至少以 UI 開頭的系統(tǒng)調(diào)用會(huì)是在主線程上被調(diào)用的,比如上面的 UIApplicationDidReceiveMemoryWarningNotification。
Happy coding!