程序员首页投稿(暂停使用,暂停投稿)iOS Developer

NSNotification和NSNotificationCen

2017-12-15  本文已影响175人  大鹏鸟

以下内容基本上基于苹果官方文档,可能会有些许地方翻译不准确,欢迎指正!
个人实验代码在这里

一、需求(为什么要通知)

对于通知,苹果官方文档这样说:

The standard way to pass information between objects is message passing—one object invokes the method of another object. However, message passing requires that the object sending the message know who the receiver is and what messages it responds to. At times, this tight coupling of two objects is undesirable—most notably because it would join together two otherwise independent subsystems. For these cases, a broadcast model is introduced: An object posts a notification, which is dispatched to the appropriate observers through an NSNotificationCenter object, or simply notification center.

简单来说,一般的方法调用的原理是消息传递,这样的调用需要知道把消息发给谁,以及消息的反馈,有时候,这会造成高耦合。为了解决这种高耦合的现象,一种广播模式出现了。

二、和代理的区别

相信这是去面试的小伙伴都快听吐了的面试题,关于这个,苹果官方也给出了答案!!!

  • Any number of objects may receive the notification, not just the delegate object. This precludes returning a value.
  • An object may receive any message you like from the notification center, not just the predefined delegate methods.
  • The object posting the notification does not even have to know the observer exists.

英语不好的就凑活着听听我这个英语更不好的人的翻译吧:

  • 任何对象都能收到通知,而不单单是代理对象。但是没有返回值。
  • 一个对象可能会从通知中心收到各种形式的消息,而不单单是代理方法中定义好的。
  • 发送通知的对象不需要知道接收对象是否存在。

三、通知中心 Notification Centers

通知中心用来负责管理发送和接收通知。
Cocoa框架包括了两种类型的通知中心:

  • The NSNotificationCenter class manages notifications within a single process.
  • The NSDistributedNotificationCenter class manages notifications across multiple processes on a single computer.

即:

  • NSNotificationCenter类在单一进程管理通知
  • NSDistributedNotificationCenter类在一个电脑上管理多个进程的多个通知

1、NSNotificationCenter

每个进程都有一个默认的通知中心,可以通过代码[NSNotificationCenter defaultCenter]类方法获取。该通知中心只能处理一个进程里的多个通知。如果在同一台机器上的多个线程之间做数据交流,应使用分布式通知中心NSDistributedNotificationCenter。

- (void)addNotificationForSync {
    [self addNotification1];
    [self addNotification2];
}

- (void)postNotificationInSync {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"receiveNotification1" object:nil];
    NSLog(@"%s",__func__);
    [[NSNotificationCenter defaultCenter] postNotificationName:@"receiveNotification2" object:nil];
}

- (void)addNotification1 {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification1) name:@"receiveNotification1" object:nil];
}

- (void)addNotification2 {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification2) name:@"receiveNotification2" object:nil];
}

- (void)receiveNotification1 {
    NSLog(@"beforeSleep:%s",__func__);
    sleep(2);
    NSLog(@"afterSleep:%s",__func__);
}

- (void)receiveNotification2 {
    NSLog(@"%s",__func__);
}

触发通知后,打印结果如下:

2017-12-14 17:20:17.340362+0800 ZPZNotificationPractice[10721:433042] beforeSleep:-[ZPZNotiSyncViewController receiveNotification1]
2017-12-14 17:20:19.340823+0800 ZPZNotificationPractice[10721:433042] afterSleep:-[ZPZNotiSyncViewController receiveNotification1]
2017-12-14 17:20:19.341084+0800 ZPZNotificationPractice[10721:433042] -[ZPZNotiSyncViewController postNotificationInSync]
2017-12-14 17:20:19.341272+0800 ZPZNotificationPractice[10721:433042] -[ZPZNotiSyncViewController receiveNotification2]

这里在通知一接收到通知后,睡眠了两秒;可以发现打印结果和预期一样的。

//主线程添加
- (void)addNotificationForThread {
    [self addNotification1];
    [self addNotification2];
}

- (void)postNotificationInThreads {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        NSLog(@"发送receiveNotification1:%@",[NSThread currentThread]);
        [[NSNotificationCenter defaultCenter] postNotificationName:@"receiveNotification1" object:nil];
    });
    dispatch_async(queue, ^{
        NSLog(@"发送receiveNotification2:%@",[NSThread currentThread]);
        [[NSNotificationCenter defaultCenter] postNotificationName:@"receiveNotification2" object:nil];
    });
}

- (void)addNotification1 {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification1) name:@"receiveNotification1" object:nil];
}

- (void)addNotification2 {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification2) name:@"receiveNotification2" object:nil];
}

- (void)receiveNotification1 {
    NSLog(@"接收%s,%@",__func__,[NSThread currentThread]);
    sleep(2);
}

- (void)receiveNotification2 {
    NSLog(@"接收%s,%@",__func__,[NSThread currentThread]);
}

打印结果如下图:


通知和线程关系.png

2、NSDistributedNotificationCenter

每个进程有一个分布式的通知中心,可以通过代码[NSDistributedNotificationCenter defaultCenter]获得。

发送一个分布式的通知的操作是昂贵的。该通知会被发送到一个系统级的服务器,然后再把它发送给注册了该通知的对象或者给某个对象注册该通知。但是有个潜在的因素,在不同进程间发送通知和接收通知是不可控的,如果服务队列满了,通知会被抛弃。

分布式通知的分发是通过进程里的run loop实现的。进程必须运行一个处于common模式下的runloop去接收分布式通知。如果接收进程是多线程的,一般会被分发到主线程,当然,其他线程有时也能接收通知。

四、Notification Queues(通知队列)

通知队列作用:将通知和异步发送通知合并

1、通知队列基础知识

使用NSNotificationCenter的postNotification: 方法或者它的变形体,可以直接将通知发送到通知中心里,这时候,他的触发和执行都是同步的(如前面所说)。通知队列会持有该通知对象,并遵循先入先出的规则,当一个通知到了队列的头部,该队列就会将该通知发送给通知中心,该通知中心会轮流发送该通知给接收者。

每个线程都有一个默认的通知队列,该队列连接着默认的通知中心。但是你可以自己创建自己的通知队列,每个通知中心和线程可以拥有多个通知队列。

//主线程添加
- (void)addNotificationForThread {
    [self addNotification1];
    [self addNotification2];
}

- (void)postNotificationInThreads {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        
        NSLog(@"发送receiveNotification1的thread:%@,queue:%@",[NSThread currentThread],[NSNotificationQueue defaultQueue]);
        [[NSNotificationCenter defaultCenter] postNotificationName:@"receiveNotification1" object:nil];
    });
    dispatch_async(queue, ^{
        NSLog(@"发送receiveNotification2的thread:%@,queue:%@",[NSThread currentThread],[NSNotificationQueue defaultQueue]);
        [[NSNotificationCenter defaultCenter] postNotificationName:@"receiveNotification2" object:nil];
    });
    dispatch_async(queue, ^{
        
        NSLog(@"发送receiveNotification1的thread:%@,queue:%@",[NSThread currentThread],[NSNotificationQueue defaultQueue]);
        [[NSNotificationCenter defaultCenter] postNotificationName:@"receiveNotification1" object:nil];
    });
    dispatch_async(queue, ^{
        NSLog(@"发送receiveNotification2的thread:%@,queue:%@",[NSThread currentThread],[NSNotificationQueue defaultQueue]);
        [[NSNotificationCenter defaultCenter] postNotificationName:@"receiveNotification2" object:nil];
    });
}

- (void)addNotification1 {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification1) name:@"receiveNotification1" object:nil];
}

- (void)addNotification2 {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification2) name:@"receiveNotification2" object:nil];
}

- (void)receiveNotification1 {
    NSLog(@"接收%s,%@,queue:%@",__func__,[NSThread currentThread],[NSNotificationQueue defaultQueue]);
    sleep(2);
}

- (void)receiveNotification2 {
    NSLog(@"接收%s,%@,queue:%@",__func__,[NSThread currentThread],[NSNotificationQueue defaultQueue]);
}

其输出为:


队列与线程.png
从前面我们知道在哪个线程发送通知,接收者就会在哪个线程执行操作,从上图可以看出,同一个线程只有一个队列时,发送和接收在同一个队列。如果代码如下呢:
- (void)postNotificationInQueue {
    NSNotification * noti = [NSNotification notificationWithName:@"receiveNotification1" object:nil];
    NSNotificationQueue * queue = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
    NSLog(@"发送receiveNotification1的thread:%@,queue:%@,mainQueue:%@",[NSThread currentThread],queue,[NSNotificationQueue defaultQueue]);
    [queue enqueueNotification:noti postingStyle:NSPostWhenIdle];
    NSLog(@"我先继续走了!!!");
}
多队列.png
这里在主线程自己创建了一个队列queue,并由它触发通知,但是接收通知所在的队列却不相同。

2、异步发送通知

官方如此描述:

With NSNotificationQueue’s enqueueNotification:postingStyle:and enqueueNotification:postingStyle:coalesceMask:forModes: methods, you can post a notification asynchronously to the current thread by putting it in a queue. These methods immediately return to the invoking object after putting the notification in the queue.

使用上面的两个方法,你可以在当前线程通过将通知加入queue中来实现异步。这些方法在将通知加入队列后会立即返回。

通知队列是否为空以及队列中的通知发送依赖于触发的style和runloop的mode。只有在mode相同的时候通知才会触发,否则,会一致处于等待状态。style有三种:NSPostASAP, NSPostWhenIdle, and NSPostNow
具体的使用如下:

消息的接收和处理如下:

- (void)receiveNotification1 {
    NSLog(@"接收前%s,%@,queue:%@",__func__,[NSThread currentThread],[NSNotificationQueue defaultQueue]);
    sleep(2);
    NSLog(@"接收后%s,%@,queue:%@",__func__,[NSThread currentThread],[NSNotificationQueue defaultQueue]);
}
- (void)postNotificationInQueue {
    NSNotification * noti = [NSNotification notificationWithName:@"receiveNotification1" object:nil];
    NSNotificationQueue * queue = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
    [queue enqueueNotification:noti postingStyle:NSPostNow];
    NSLog(@"我先继续走了!!!");
}

打印如下:


NSPostNow.png
此时和同步执行没有什么区别
- (void)postNotificationInQueue {
    NSNotification * noti = [NSNotification notificationWithName:@"receiveNotification1" object:nil];
    NSNotificationQueue * queue = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
    [queue enqueueNotification:noti postingStyle:NSPostASAP];
    NSLog(@"我先继续走了!!!");
}

打印如下:


NSPostASAP.png
此时不再等待,添加到队列后就继续向下走了
- (void)postNotificationInQueue {
    NSNotification * noti = [NSNotification notificationWithName:@"receiveNotification1" object:nil];
    NSNotificationQueue * queue = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
    [queue enqueueNotification:noti postingStyle:NSPostWhenIdle];
    NSLog(@"我先继续走了!!!");
}
NSPostWhenIdle.png
此时不再等待,添加到队列后就继续向下走了
虽然这样可以实现异步,但是这里容易产生一个坑,苹果官方有如下描述:

When the thread where a notification is enqueued terminates before the notification queue posts the notification to its notification center, the notification is not posted.

简单来说,就是如果当前添加到通知队列的线程如果不存在了,那么通知就不会再发送。
因为NSPostASAP和 NSPostWhenIdle都需要等待时机(匹配runloop的模式),在此期间,线程容易被销毁或者等不到匹配的模式,从而导致通知发送不出去,如下代码:

- (void)postNotificationInQueueSpecial {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        
        NSNotification * noti = [NSNotification notificationWithName:@"receiveNotification1" object:nil];
        NSNotificationQueue * queue = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
        [queue enqueueNotification:noti postingStyle:NSPostWhenIdle];
    });
}

你会发现没有任何输出!!!
因为执行完enqueueNotification:方法后,不会再等待,而是会继续向下走,此时,执行完后,该线程就会被回收了,所以该通知自然不会发送!(至少我是这么理解的,不知道对不对)

3、合并通知

在某些情况下,如果某个事件至少发生一次,您可能希望发布一个通知,但即使同一时间事件多次发生,您也希望只发布一个通知。
这个时候可以通过设置方法enqueueNotification:postingStyle:coalesceMask:forModes:里的第三个参数实现。

- (void)coalescingNotification {
    NSNotification * noti1 = [NSNotification notificationWithName:@"receiveNotification2" object:nil];
    NSNotificationQueue * queue1 = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
    [queue1 enqueueNotification:noti1 postingStyle:NSPostWhenIdle coalesceMask:NSNotificationCoalescingOnName forModes:nil];
    [queue1 enqueueNotification:noti1 postingStyle:NSPostWhenIdle];
}
上面的代码执行了两次添加通知,一般来说,发送几次通知,接收者就会执行几次通知,但是这里只有一次,说明成功的将同一线程下相同的通知合并了!!!(添加几次,就会接收几次,然后执行次数视情况而定)

这里只是根据通知名字做的判断,还有其他两种,可以自己去探索。

五、注册和发送通知

注册的通知需要在合适的时机移除,最迟在deallocated方法里移除。

1、注册和发送当前程序的通知(NSNotificationCenter)

注册通知调用方法addObserver:selector:name:object:即可。注册的时候只需要有name和object中的一个就可以。

注意:添加发送必须对应,nil可以兼容更多,对应于添加时的位置上为nil的地方,发送相对应地可以为任何值,否则添加时指定了哪个,那么发送的时候也必须对应。

2、注册分发通知(Distributed Notifications)

略!!!!

上一篇下一篇

猜你喜欢

热点阅读