Objective-C 代码
[[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil userInfo:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:TEST_NOTIFICATION object:nil];
- (void)processNotification:(NSNotification *)notification {
}
Swift 代码
NotificationCenter.default.post(name: NSNotification.Name(rawValue: NOTI_WXLOGIN_AUTHORIZED), object: authResp.code)
NotificationCenter.default.addObserver(self, selector: #selector(handleWeChatLoginSuccess(_:)), name: NSNotification.Name(rawValue: NOTI_WXLOGIN_AUTHORIZED), object: nil)
@objc private func handleWeChatLoginSuccess(_ noti:NSNotification) {
}
线程安全
NSNotification 是线程安全的,但也不是绝对安全。例如:
- 当我们注册一个观察者时,通知中心会持有观察者的一个弱引用,来确保观察者是可用的。
- 调用dealloc操作会让 Observer 对象的引用计数减为 0,这时对象会被释放掉。
- 其他线程发送一个通知,如果此时 Observer 还未被释放,则会向其转发消息,并执行回调方法。而如果在回调执行的过程中对象被释放了,就会出现 crash。
几点说明
-
NSNotification 消息的同步性
- NSNotification 使用的是同步操作,即如果程序中 A 位置 post 了一个 NSNotification,在 B、C 位置注册了 Observer,通知发出后,必须等到 B、C 位置的通知回调执行完毕才能返回到 A 处继续往下执行
- 如果想让 NSNotification 的 post 处 和 observer 处异步执行,可以通过 NSNotificationQueue 实现
-
多个观察者的执行顺序
对于同一个通知,如果注册了多个观察者,则多个观察者的执行顺序和它们的注册顺序是保持一致的
-
NSNotification 通知转发线程
- NSNotificationCenter 在转发 NSNotification 消息时,在哪个线程 post,就在哪个线程转发。即,不管 observer 是在哪个线程,observer 的回调方法执行线程都和 post 的线程保持一致
- 如果想让 post 的线程和转发的线程不同,可以通过 NSNotification 重定向技术实现。
// NSNotification 重定向技术 /* 思路: 1. 自定义个通知队列(注意不是 NSNotificationQueue 对象而是一个数组),让这个队列去维护那些需要重定向的 Notification,我们仍然像平常一样去注册一个通知的观察者 2. 当 Notification 来了时,先看 post 这个 Notification 的线程是不是我们所期望的线程,如果不是,则将这个 Notification 存储到我们的队列中,并发送一个信号(signal)到期望的线程中,来告诉这个线程需要处理一个 Notification。 3. 指定的线程收到信号后,将 Notification 从队列中移除,并进行处理 */ @property (nonatomic) NSMutableArray *notifications; // 通知队列 @property (nonatomic) NSThread *notificationThread; // 期望线程 @property (nonatomic) NSLock *notificationLock; // 用于对通知队列加锁的锁对象,避免线程冲突 @property (nonatomic) NSMachPort *notificationPort; // 用于向期望线程发送信号的通信端口 - (void)postNotify { NSLog(@"current thread = %@", [NSThread currentThread]); // 初始化 self.notifications = [[NSMutableArray alloc] init]; self.notificationLock = [[NSLock alloc] init]; self.notificationThread = [NSThread currentThread]; self.notificationPort = [[NSMachPort alloc] init]; self.notificationPort.delegate = self; // 往当前线程的run loop添加端口源 // 当Mach消息到达而接收线程的run loop没有运行时,则内核会保存这条消息,直到下一次进入run loop [[NSRunLoop currentRunLoop] addPort:self.notificationPort forMode:(__bridge NSString *)kCFRunLoopCommonModes]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:TEST_NOTIFICATION object:nil]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil userInfo:nil]; }); } - (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]; NSLog(@"11111current thread = %@", [NSThread currentThread]); } else { // Process the notification here; NSLog(@"current thread = %@", [NSThread currentThread]); NSLog(@"process notification"); } } - (void)handleMachMessage:(void *)msg { NSLog(@"33333current thread = %@", [NSThread currentThread]); [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]; }
- addObserver 和 removeObserver 必须成对出现
注意
- 尽量在一个线程中处理通知相关的操作,大部分情况下,这样做都能确保通知的正常工作。不过,我们无法确定到底会在哪个线程中调用 dealloc 方法,所以这一点还是比较困难。
- 注册监听都时,使用基于block的API。这样我们在block还要继续调用self的属性或方法,就可以通过weak-strong的方式来处理。具体大家可以改造下上面的代码试试是什么效果。
- 使用带有安全生命周期的对象,这一点对象单例对象来说再合适不过了,在应用的整个生命周期都不会被释放。
- 使用代理。
网友评论