Notification,项目中使用还是蛮多的,post发送通知,addObserver监听接收通知,听起来很简单,对吧。但是还是会有些大家可能会忽视的地方。
同步or异步
[NotificationCenter defaultCenter] postNotification],这种方式是同步
的,并且在哪个线程发,就在哪个线程收。
同步的意思,就是消息接收者全部处理完消息之后,post这方才会继续往下执行,因此,尽量不要做太耗时的操作。
由于消息收和发都在同一个线程中
。所以,尽量在主线程中
post,不然会引起不必要的麻烦,ui刷新问题,崩溃问题等等。
addObserver调用多次
addObserver如果添加多次,当post的时候,也会收到多次。类似这种:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:nil];
observer的移除
这个是老生常谈了,一定记得要移除,否则崩溃很容易发生。不过在iOS 9以后,不需要手动移除。
If your app targets iOS 9.0 and later or OS X v10.11 and later, you don't need to unregister an observer in its deallocation method。
异步通知
异步的好处,不必等待所有的消息处理者处理完成,可以立马返回。
如果要发送异步通知,可以使用NSNotificationQueue
,将通知enqueueNotification
之后,会在合适的时候将通知发送给NotificationCenter,NotificationCenter会真正的将消息post出去。
[[NSNotificationQueue defaultQueue] enqueueNotification:[NSNotification notificationWithName:@"task" object:self] postingStyle:NSPostWhenIdle];
可以指定runloop mode,当runloop处理该种mode的时候,才会发送通知。
也可以指定发送通知的时机,有如下3种。
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1,
NSPostASAP = 2,
NSPostNow = 3
};
NSPostWhenIdle,在runloop空闲时发送,当runloop要退出时,不会发送。
NSPostASAP:Posting As Soon As Possible,在runloop的当前迭代完成时发送给通知中心,但是当前mode和设定的mode要一致。
NSPostNow:就是同步调用。
聚合发送
当在一段时间内,enqueue了多个通知,系统不会每个都发送,如果在队列中已有该种通知,则不会进入队列,只保留第一个通知。有如下3中可选方式。
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
NSNotificationNoCoalescing = 0, // 不聚合
NSNotificationCoalescingOnName = 1, // 根据通知名聚合
NSNotificationCoalescingOnSender = 2 // 根据发送者聚合
};
NSNotificationCoalescingOnName:根据通知名称来聚合,如果在一段时间内,
NotificationName="UpdateMyProfileNotification"有多个,则将他们聚合起来,只发送一个。
NSNotificationCoalescingOnSender:根据发送方来聚合。
我做了下测试,的确只收到一次通知。并且是第一个通知。
for (int i = 0; i < 2; i++) {
NSNotification *notification = [NSNotification notificationWithName:@"TestNotification" object:@(i)];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification
postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil];
}
在指定线程接收通知
上面说到,收发都在一个线程中,如果想要做到,在某个指定的线程接收通知,该如何做呢?苹果文档上提及了实现思路。
先简单介绍下mach port,它主要用来线程间通信。简单来说,就是接收线程中注册NSMachPort,在另外的线程中使用此port发送消息,则注册线程会收到相应消息,调用handleMachMessage来处理。
主要思路:
定义一个中间对象NotificationHandler,用来专门接收通知,包括一个队列,接收通知的线程,mach port,lock。
首先NotificationHandler会注册一个通知,对应的处理函数为processNotification,当在其他线程中post时,processNotification会被调用,进行如下处理。如果收到的通知跟指定的线程一样,则处理消息,反之,则添加到队列,同时通过port发送消息给指定线程。注意
多线程中,对队列的处理,要加锁
。指定线程收到回调handleMachMessage,首先会将通知删除,然后调用processNotification进行处理,继续以上过程。
- (instancetype)init {
if (self = [super init]) {
[self setUpThreadingSupport];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"notification" object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)setUpThreadingSupport
{
if (self.notifications) {
return;
}
self.notifications = [NSMutableArray new];
self.lock = [NSLock new];
self.thread = [NSThread currentThread];
self.port = [NSMachPort new];
[self.port setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:self.port forMode:(__bridge NSString *) kCFRunLoopCommonModes];
}
- (void)handleMachMessage:(void *)msg
{
[self.lock lock];
// 由于大量port message在同时被发送时,可能会被丢弃,为了防止没有处理到,这里遍历数组来进行处理。
while ([self.notifications count]) {
NSNotification *notification = [self.notifications objectAtIndex:0];
[self.notifications removeObjectAtIndex:0];
[self.lock unlock];
[self processNotification:notification];
[self.lock lock];
}
[self.lock unlock];
}
- (void)processNotification:(NSNotification *)notification
{
NSThread *ct = [NSThread currentThread];
// 不是指定线程
if (ct != _thread) {
[self.lock lock];
// 添加到队列
[self.notifications addObject:notification];
[self.lock unlock];
// 通过mach port发送消息
[self.port sendBeforeDate:[NSDate date] components:nil from:nil reserved:0];
}else{
NSLog(@"process notification %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
}
}
如果,我们想要在主线程中接收通知,在viewDidLoad中。
- (void)viewDidLoad {
self.notificationHandler = [NotificationHandler new];
// 在子线程中post,会在主线程中收到
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"post notification %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];
});
}
或者,需要在某个子线程中接收,可以这样。
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(startThread) object:nil];
[self.thread start];
}
- (void)startThread {
NSLog(@"startThread %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
self.notificationHandler = [NotificationHandler new];
// 在另外的子线程中发送通知dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"post notification %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];
});
// 线程中需要自己启动runloop
[[NSRunLoop currentRunLoop] run];
}
参考:
Notifications