NSNotificationCenter 和 NSNotificationQueue
通知中心和通知队列,需要明确的是队列只是缓存通知,并不实际推送通知,但是根据 NSPostingStyle
和 NSNotificationCoalescing
的选择,通知队列可以决定向通知中心传递通知的时机以及是否合并相关的通知。
默认通知队列
NSNotificationQueue
提供了一个类属性 defaultQueue
来获取当前线程默认的通知队列,该队列相关联的通知中心是应用默认的通知中心,并且提供了下面的方法来向队列中添加通知。
- (void)enqueueNotification:(NSNotification *)notification
postingStyle:(NSPostingStyle)postingStyle
coalesceMask:(NSNotificationCoalescing)coalesceMask
forModes:(nullable NSArray<NSRunLoopMode> *)modes;
在使用这个方法时,需要知道的是,使用 NSNotificationCenter
直接推送通知时,通知的执行线程是推送通知的线程,知道这一点对通知队列的理解和使用十分重要。
因为使用上面的方法向队列中添加通知时,涉及到运行循环的模式,而运行循环在子线程中是默认关闭的。所以,除非是在主线程中调用上面的方法,否则需要注意子线程的运行循环是否开启,当然,如果使用 NSPostNow
来表示立即推送通知,那么这和主动调用 NSNotificationCenter
的推送方法效果一样,也不需要考虑运行循环的模式。
若是使用 NSPostASAP
(as soon as possible) 和 NSPostWhenIdle
来延后通知的推送,那么就需要考虑到当前线程的运行循环是否开启,以及其是否处于预期的循环模式中。
如下面的例子,如果不开启运行循环,或者开启循环的模式与添加通知时指定的模式不一致时,通知都不会发出。
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(test:) name:@"test" object:nil];
}
- (void)btnClick {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSNotificationQueue *queue = [NSNotificationQueue defaultQueue];
NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);
[NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);
}];
NSNotification *notification = [NSNotification notificationWithName:@"test" object:self];
[queue enqueueNotification:notification postingStyle:NSPostASAP];
notification = [NSNotification notificationWithName:@"test" object:nil];
/*
[queue enqueueNotification:notification postingStyle:NSPostASAP];
[queue enqueueNotification:notification
postingStyle:NSPostASAP
coalesceMask:NSNotificationCoalescingOnName|NSNotificationCoalescingOnSender
forModes:@[NSRunLoopCommonModes]];
*/
[queue enqueueNotification:notification
postingStyle:NSPostWhenIdle
coalesceMask:NSNotificationCoalescingOnName
forModes:@[NSRunLoopCommonModes]];
[NSRunLoop.currentRunLoop run];
/*
NSTimer *timer = [NSTimer timerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);
}];
[NSRunLoop.currentRunLoop addTimer:timer forMode:@"MyRunloopMode"];
[NSRunLoop.currentRunLoop runMode:@"MyRunloopMode" beforeDate:NSDate.distantFuture];
*/
});
}
- (void)test:(NSNotification *)notification {
NSLog(@"%@ %@ %@",notification,NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);
}
实际上,使用队列添加通知时,可以根据条件聚合缓存的通知,如果符合聚合条件,那么保留的是已缓存的那个通知,并且判断是否能够聚合的条件并不包括运行循环。这就意味着添加的通知在聚合时,会忽略运行循环的不同,这就需要我们自己考虑其中的影响了。
如下面的例子,在 MyRunloopMode
循环模式下,并没有接收到通知,因为在该循环模式添加的通知根据 NSNotificationCoalescingOnName
聚合条件,同 NSDefaultRunLoopMode
模式下的通知相同,所以通知并不会被添加到队列中。
当 5 秒后,运行循环重新开启,默认是 NSDefaultRunLoopMode
模式,第一个添加的通知便会发出。
- (void)btnClick {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSNotificationQueue *queue = [NSNotificationQueue defaultQueue];
NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);
[NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);
}];
NSNotification *notification = [NSNotification notificationWithName:@"test" object:self];
[queue enqueueNotification:notification postingStyle:NSPostASAP];
notification = [NSNotification notificationWithName:@"test" object:nil];
NSTimer *timer = [NSTimer timerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);
[queue enqueueNotification:notification
postingStyle:NSPostASAP
coalesceMask:NSNotificationCoalescingOnName
forModes:@[NSRunLoopCommonModes,@"MyRunloopMode"]];
}];
[NSRunLoop.currentRunLoop addTimer:timer forMode:@"MyRunloopMode"];
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:5];
[NSRunLoop.currentRunLoop runMode:@"MyRunloopMode" beforeDate:date];
[NSRunLoop.currentRunLoop run];
});
}
自定义通知队列
使用下面的方法自定义通知队列,既然要自定义队列,那么大多也就意味着我们不想使用 NSNotificationCenter
类属性 defaultCenter
返回的默认的通知中心,而是自己指定推送通知队列中的通知的通知中心。
- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter;
实际上,defaultCenter
作为应用默认的通知中心,负责承接所有的系统的通知,也是我们应用中常用的通知中心。但是,这同样意味着接收每一个通知推送任务时,通知中心会去遍历注册表中的所有监听者,判断其是否需要执行相关任务。这对于通知繁多,性能敏感的应用是十分不利的。如果监听者要执行的任务十分耗时,那么便会阻塞当前线程,而该线程若恰好是主线程时,结果就相当不美好了。所以自定义自己的通知中心,分门别类的将通知发送给指定的通知中心去推送,可以提高性能和功能的模块化。
如果对通知的时效性要求较高,则可以配合自定义队列使用,从而过滤掉无效的通知。
每个线程都可以获取到同默认通知中心相关联的通知队列,即说明一个通知中心可以被多个通知队列相关联。但实际上,通知队列和通知中心和线程都是不想关的,即使在子线程中创建通知队列,在主线程中同样可以使用其添加通知。
这里所说的通知中心,是面向开发者的,同面向用户的通知中心是两个概念。