NSNotification和NSNotificationCenter

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

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

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

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。

  • 通知中心给observers发送通知是同步发送的,也就是说当发送通知的时候,直到所有的observers收到通知并处理完毕后,才会继续执行。
    代码如下:
- (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]

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

  • 如果想异步发送通知,可以使用通知队列(见下文“四、Notification Queues”)

  • 在多线程工程下,通知所执行的线程和触发通知的线程相同,和注册通知时的线程无关。

//主线程添加
- (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
  • NSPostASAP:“Posting As Soon As Possible”,当runloop模式和request的mode一样的时候执行。

  • NSPostWhenIdle:“Posting When Idle”,当runloop处于等待状态时执行

  • NSPostNow :“Posting Immediately”,接收到后立马执行,和postNotification:方法一样。

具体的使用如下:

消息的接收和处理如下:

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

打印如下:


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

打印如下:


NSPostASAP.png
此时不再等待,添加到队列后就继续向下走了
  • style=NSPostWhenIdle
- (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的地方,发送相对应地可以为任何值,否则添加时指定了哪个,那么发送的时候也必须对应。

  • 在添加的地方代码为:[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotiForObjectIsNil:) name:@"receiveNotiForObjectIsNil" object:self];,这里的self为ZPZRegisteNotiViewController,那么发送的地方代码必须为:[[NSNotificationCenter defaultCenter] postNotificationName:@"receiveNotiForObjectIsNil" object:_addVC];,这里的_addVC和上面的self相同。如果发送的地方的object为nil,则接收不到这里的消息。
  • 如果在添加的地方为:[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotiForObjectIsNil:) name:@"receiveNotiForObjectIsNil" object:nil];,那么在发送的地方可以是[[NSNotificationCenter defaultCenter] postNotificationName:@"receiveNotiForObjectIsNil" object:_addVC];,也可以是[[NSNotificationCenter defaultCenter] postNotificationName:@"receiveNotiForObjectIsNil" object:nil];
  • 如果在添加的地方name为空:[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotiForObjectIsNil:) name:nil object:self];,这里的self为ZPZRegisteNotiViewController,那么发送的地方为:[[NSNotificationCenter defaultCenter] postNotificationName:nil object:_addVC];或者[[NSNotificationCenter defaultCenter] postNotificationName:@"receiveNotiForObjectIsNil" object:_addVC];,这里的name可以为任何值,但是object必须和前面的self一样。
  • 如果添加的代码为:[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotiForObjectIsNil:) name:nil object:nil];,则这里会接收到所有的通知!!!这里从ZPZRegisteNotiViewController push到ZPZPostNotiViewController时,接收到了很多通知啊(下面两张是一起的):
    push1.png

    push2.png

    想继续研究的可以继续,我这里暂时点到为止!!!

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

略!!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • 简述 NSNotification 是iOS中一个消息通知类,存储消息的一些信息;NSNotificationCe...
    小白进城阅读 1,065评论 1 5
  • 问题的背景 IOS中委托模式和消息机制基本上开发中用到的比较多,一般最开始页面传值通过委托实现的比较多,类之间的传...
    随风__陈坪__阅读 26,189评论 10 48
  • 转载自南峰子的技术博客 一个NSNotificationCenter对象(通知中心)提供了在程序中广播消息的机制,...
    我消失1314阅读 760评论 0 2
  • By 若愚 1 以前总觉得年轻,可以肆意的熬夜透支自己的身体,每每到凌晨2点还能不睡觉。早饭也不按时,然后胃病来了...
    若愚girl阅读 701评论 8 15
  • 过临平,一时起兴,从旁换心情。木桥铺水中,残荷观人行,山净晨清涤魂灵。夏景阑珊,秋意满,免却俗事牵。 步吟廊,诗满...
    玖玲阅读 352评论 0 5