osx进程间通信之NSDistributedNotificationCenter

部分内容转自:http://www.jianshu.com/p/2503e3e5fc64

一、NSNotificationCenter(通知中心)

通知中心是整个通知机制的关键所在,它管理着监听者的注册和注销,通知的发送和接收。通知中心维护着一个通知的分发表,把所有通知发送者发送的通知,转发给对应的监听者们。Cocoa中有2种通知中心,一种是NSNotificationCenter,它只能处理一个程序内部的通知,另一种是NSDistributedNotificationCenter(mas OS上才有),它能处理一台机器上不同程序之间的通知。

NSDistributedNotificationCenter

每个任务都有一个缺省的分布式通告中心,您可以通过NSDistributedNotificationCenterdefaultCenter类方法来访问。这个分布式通告中心负责处理同一个机器的不通任务之间的通告。如果需要实现不同机器上的任务间通讯,请使用分布式对象。

发送分布式通告是一个开销昂贵的操作。通告会被发送给一个系统级别的服务器,然后再分发到注册了该分布式通告的对象所在的任务中。发送通告和通告到达另一个任务之间的延迟是很大的。事实上,如果发出的通告太多,以致于充满了服务器的队列,就可能被丢弃。

分布式通告通过任务的运行循环来分发。任务必须运行在某种“常见”模式的运行循环下,比如NSDefaultRunLoopMode模式,才能接收分布式通告。如果接收通告的任务是多线程的,则不要以通告会到达主线程作为前提。通告通常被分发到主线程的运行循环上,但是其它线程也可以接收通告。

尽管常规的通告中心允许任何对象作为通告对象(也就是通告封装的对象),分布式通告中心只支持将NSString对象作为它的通告对象。由于发出通告的对象和通告的观察者可能位于不同的任务中,通告不能包含指向任意对象的指针。因此,分布式通告中心要求通告使用字符串作为通告对象。通告的匹配就是基于这个字符串进行的,而不是基于对象指针。

1、应用1:当收到“PiaoYun Notification”通知时候,会发出“ShowWindow Notification”通知

 1 - (void)callbackWithNotification:(NSNotification *)myNotification;
 2 {
 3     if([myNotification.name isEqualToString:@"PiaoYun Notification"]){
 4         NSString *observedObject = @"com.chinapyg.notification";
 5         NSDistributedNotificationCenter *center =
 6         [NSDistributedNotificationCenter defaultCenter];
 7         [center postNotificationName: @"ShowWindow Notification"
 8                               object: observedObject
 9                             userInfo: nil /* no dictionary */
10                   deliverImmediately: NO];
11     }
12     NSLog(@"Notification Received");
13 }
14
15 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
16     NSString *observedObject = @"com.chinapyg.notification";
17     // 处理单个计算机上不同的进程之间的通知
18     NSDistributedNotificationCenter *center =
19     [NSDistributedNotificationCenter defaultCenter];
20     [center addObserver: self
21                selector: @selector(callbackWithNotification:)
22                    name: nil/*@"PiaoYun Notification"*/
23                  object: observedObject];
24 }
- (void)callbackWithNotification:(NSNotification *)myNotification;
{
    if([myNotification.name isEqualToString:@"ShowWindow Notification"]){
        dispatch_async(dispatch_get_main_queue(), ^{
            self.testWindowController = [[TestWindowController alloc]initWithWindowNibName:@"TestWindowController"];
            [self.testWindowController showWindow:self];
        });
    }
    NSLog(@"Notification Received1");
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    [self.window close];
    NSDistributedNotificationCenter *center =
    [NSDistributedNotificationCenter defaultCenter];
    NSString *observedObject = @"com.chinapyg.notification";
    [center addObserver: self
               selector: @selector(callbackWithNotification:)
                   name: nil/*@"PiaoYun Notification"*/
                 object: observedObject];
    NSLog(@"notification send");
    [center postNotificationName: @"PiaoYun Notification"
                          object: observedObject
                        userInfo: nil /* no dictionary */
              deliverImmediately: NO];

}

2、应用程序2运行起来,先发送“PiaoYun Notification”通知,当收到“ShowWindow Notification”时候,显示窗口。

二、NSNotificationQueue(通知队列)

通知队列,顾名思义,就是放通知(Notification对象)的队列。一般的发送通知方式,通知中心收到发送者发出的通知后,会立刻分发给监听者,但是如果把通知放在通知队列中,通知就可以等到某些特定时刻再发出,比如等到之前发出的通知在runloop中处理完,或者runloop空闲的时候。它就像通知中心的缓冲池,把一些不着急发出的通知存在通知队列中。

这些存储在通知队列中的通知会以先进先出的方法发出(FIFO),放一个通知到达队列的头部,它将被通知队列转发给通知中心,然后通知中心再分发给相应的监听者们。

每个线程有一个默认的通知队列,它和通知中心关联着,你也可以自己为线程或者通知中心创建多个通知队列。

通知队列给通知机制提供了2个重要的特性:通知合并和异步发送通知

通知合并

有时候,对一个可能会发生不止一次的事件,你想发送一个通知去通知某些对象做一些事,但当这个事件重复发生时,你又不想再发送同样的通知了。

你可能会这样做,设置一个flag来决定是否还需要发送通知,当第一个通知发出去时,把这个flag设置为不在需要发送通知,那么当相同的事件再发生时,就不会发送相同的通知了,看起来很美好,不过这样是不能达到我们的目的的,还是那个问题,因为普通的通知发送方式默认是同步的,通知的发送者需要等到所有的监听者都接收并处理完消息才能接着处理接下来的业务逻辑,也就是说当第一个通知发出的时候,可能还没回来,第二个通知已经发出去了,在你改变flag的值的时候,可能已经发出去若干个通知了...

这个时候,就需要用到通知队列的通知合并功能了。使用NSNotificationQueueenqueueNotification:postingStyle:coalesceMask:forModes:方法,设置第三个参数coalesceMask的值,来指定不同的合并规则,coalesceMask有3个给定的值:

typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
    NSNotificationNoCoalescing = 0,
    NSNotificationCoalescingOnName = 1,
    NSNotificationCoalescingOnSender = 2
};

分别是不合并,按通知的名字合并,和按通知的发送者合并。

设置合并规则后再加入到通知队列中,通知队列会按照给定的合并规则,在之前入队的通知中查找,然后移除符合合并规则的通知,这样就达到了只发送一个通知的目的。

合并规则还可以用|符号连接,指定多个:

NSNotification *myNotification = [NSNotification notificationWithName:@"MyNotificationName" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:myNotification
                                          postingStyle:NSPostWhenIdle
                                          coalesceMask:NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender
                                              forModes:nil];

异步发送通知

使用通知队列的下面2个方法,将通知加到通知队列中,就可以将一个通知异步的发送到当前的线程,这些方法调用后会立即返回,不用再等待通知的所有监听者都接收并处理完。

- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSString *> *)modes;

注意:如果通知入队的线程在该通知被通知队列发送到通知中心之前结束了,那么这个通知将不会被发送了。

注意到上面第二个方法中,有一个modes参数,当指定了某种特定runloop mode后,该通知值有在当前runloop为指定mode的下,才会被发出。

通知队列发送通知有3种类型,也就是上面方法中的postingStyle参数,它有3种取值:

typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1,
    NSPostASAP = 2,
    NSPostNow = 3
};
  • NSPostASAP (尽快发送 Posting As Soon As Possible)
    以NSPostASAP风格进入队列的通知会在运行循环的当前迭代完成时被发送给通知中心,如果当前运行循环模式和请求的模式相匹配的话(如果请求的模式和当前模式不同,则通知在进入请求的模式时被发出)。由于运行循环在每个迭代过程中可能进行多个调用分支(callout),所以在当前调用分支退出及控制权返回运行循环时,通知可能被分发,也可能不被分发。其它的调用分支可能先发生,比如定时器或由其它源触发了事件,或者其它异步的通知被分发了。

    开发者通常可以将NSPostASAP风格用于开销昂贵的资源,比如显示服务器。如果在运行循环的一个调用分支过程中有很多客户代码在窗口缓冲区中进行描画,在每次描画之后将缓冲区的内容刷新到显示服务器的开销是很昂贵的。在这种情况下,每个draw...方法都会将诸如“FlushTheServer” 这样的通知排入队列,并指定按名称和对象进行合并,以及使用NSPostASAP风格。结果,在运行循环的最后,那些通知中只有一个被派发,而窗口缓冲区也只被刷新一次。

  • NSPostWhenIdle(空闲时发送)

    以NSPostWhenIdle风格进入队列的通知只在运行循环处于等待状态时才被发出。在这种状态下,运行循环的输入通道中没有任何事件,包括定时器和异步事件。以NSPostWhenIdle风格进入队列的一个典型的例子是当用户键入文本、而程序的其它地方需要显示文本字节长度的时候。在用户输入每一个字符后都对文本输入框的尺寸进行更新的开销是很大的(而且不是特别有用),特别是当用户快速输入的时候。在这种情况下,Cocoa会在每个字符键入之后,将诸如“ChangeTheDisplayedSize”这样的通知进行排队,同时把合并开关打开,并使用NSPostWhenIdle风格。当用户停止输入的时候,队列中只有一个“ChangeTheDisplayedSize”通知(由于合并的原因)会在运行循环进入等待状态时被发出,显示部分也因此被刷新。请注意,运行循环即将退出(当所有的输入通道都过时的时候,会发生这种情况)时并不处于等待状态,因此也不会发出通知。

  • NSPostNow(立即发送)

    以NSPostNow风格进入队列的通知会在合并之后,立即发送到通知中心。开发者可以在不需要异步调用行为的时候 使用NSPostNow风格(或者通过NSNotificationCenter的postNotification:方法来发送)。在很多编程环境下,我们不仅允许同步的行为,而且希望使用这种行为:即开发者希望通知中心在通知派发之后返回,以便确定观察者对象收到通知并进行了处理。当然,当开发者希望通过合并移除队列中类似的通知时,应该用enqueueNotification...方法,且使用NSPostNow风格,而不是使用postNotification:方法。

发送通知到指定线程

通知中心分发通知的线程一般就是通知的发出者发送通知的线程。但是有时候,你可能想自己决定通知发出的线程,而不是由通知中心来决定。举个栗子,在后台线程中有一个对象监听者主线程中界面上的一些变化,比如一个window的关闭或者一个按钮的点击,这时通知是在主线程中发出的,通常来说只能在主线程中接受,但是你会希望这个对象能在后台线程中接到通知,而不是主线程中。这时你就需要在这些通知本来在的线程中抓住它们,然后将它们重定向到你想要指定的线程。

一种实现思路就是实现自定义的通知队列(不是NSNotificationQueue)去保存那些通知,然后将他们重定向到你指定的线程,大致流程就是:照常用一个对象去监听一个通知,当这个通知被触发时,监听者接受到后,判断当前线程是否为处理该通知正确的线程,如果是则处理,否则,将改通知保存到我们自定义的通知队列中,然后给目标队列发送一个信号,表明这个通知需要在目标队列中处理,目标队列接受到信号后,从通知队列中取出通知并处理。

时间: 2024-10-23 13:50:47

osx进程间通信之NSDistributedNotificationCenter的相关文章

MAC OSX 进程间通信

Mac OS下的IPC方式种类很多,大约有下面几种. 1. Mach API 2. CFMessagePort 3. Distributed Objects (DO) NSDistributedNotificationCenter 4. Apple events 5. UNIX domain sockets 6. Internet sockets 或者 XPC(NSConnection) 7. Memory Mapping 还有Pipe,NSTask,消息队列,远程过程调用,通知,信号量和锁设置

iOS&amp;OSX系统初步了解

本文算是自己的一个笔记吧. 介绍: Unix于1969年诞生于贝尔实验室的计算机科学家Ken Thompson的头脑中,Thompson和Ritchie为支持游戏开发而在PDP-7上编制的实用程序成了Unix的核心——虽然直到1970年才产生Unix这个名字,1978年,第一个Unix公司(the Santa Cruz Operation,SCO)成立,同年售出第一个商用C编译器(Whitesmiths) AF_INET域与AF_UNIX域socket通信原理对比 http://blog.csd

Mac OSX (EI Capitan)搭建Caffe环境并配置python接口

Caffe是一个清晰而高效的深度学习框架,其作者是博士毕业于UC Berkeley的贾扬清.Caffe是纯粹的C++/CUDA架构,支持命令行.Python和MATLAB接口:可以在CPU和GPU直接无缝切换.我在MacbookPro(无NVIDIA显卡)上大费周章地配置了Caffe的环境,并花了许多时间配置其python接口. 一.下载Caffe github上的下载地址:https://github.com/BVLC/caffe进入到下载后的路径,并复制 Makefile.config.ex

Linux进程间通信总结

Linux进程间通信总结 1. 管道 管道是Linux支持的最初Unix IPC形式之一,具有以下特点: (1)管道是半双工的,数据只能向一个方向流动:需要双方通信时,需要建立起两个管道: (2)只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程): (3)单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中. (4)数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出

Xenomai 进程间通信方式

Xenomai 进程间通信方式分成Xenomai域内的IPC以及Xenomai域和Linux域之间的IPC, 目前采用的rtipc(RTDM驱动)的方式,主要是给用户空间提供socket接口,实时应用 通过调用对应的接口可以避免切换到Linux域而导致实时性降低.rtipc对应了三个协议: XDDP (Xenomai域和Linux域之间的IPC) IDDP和BUFP (Xenomai域内的IPC) 另外,原有的RT_PIPE机制仍旧支持,但从Xenomai 3开始就不支持了. http://ww

31、互斥锁与进程间通信

我们之前做了多进程并发,那么你们有没有发现问题.如果说多个进程共享同一个数据,比如抢火车票大家同时在客户端查看同时购买会出现什么问题呢?今天我们将讲述进程锁还有进程间通信,进程之间彼此隔离,他们需要一个第三方联系起来. 一.互斥锁 进程之间数据隔离,但是共享一套文件系统,因而可以通过文件来实现进程直接的通信,但问题是必须自己加锁处理 注意:加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行的修改,没错,速度是慢了,牺牲了速度而保证了数据安全. 1.上厕所 先举个通俗易懂

Mac OSX系统、Linux、Windows命令行教程

Mac OSX系统.Linux.Windows命令行教程 一.各系统终端的使用方法 二.各系统命令的功能 用你的终端做一些事情 (command line, Terminal, PowerShell). 一.各系统终端的使用方法 Mac OSX 在Mac OSX系统上,你应该 按住 command 键,并敲空格键. 屏幕顶部会弹出一个蓝色的"搜索框". 输入"terminal". 点击终端应用程序,这个程序的图标看起来有点像一个黑盒子. 终端就打开了. 现在你可以在

一起talk C栗子吧(第八十五回:C语言实例--使用信号进行进程间通信二)

各位看官们,大家好,上一回中咱们说的是使用信号进行进程间通信的样例,这一回咱们接着上一回的内容,继续说该样例.闲话休提.言归正转. 让我们一起talk C栗子吧. 我们在上一回中举了使用信号进行进程间通信的样例,在该样例中.我们通过终端发出信号.当进程收到该信号后让它运行系统对信号定义的默认动作.这一回.我们再来举一个使用信号进行进程间通信的样例,只是.我们发送和处理信号的方式和上一回的样例不一样.在接下来的样例中,我们在一个进程中使用kill产生信号.在另外一个进程中接收而且依照自己的方式处理

进程间通信 详解

进程间通信(IPC)介绍 进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息. IPC的方式通常有管道(包括无名管道和命名管道).消息队列.信号量.共享存储.Socket.Streams等.其中 Socket和Streams支持不同主机上的两个进程IPC. 以Linux中的C语言编程为例. 一.管道 管道,通常指无名管道,是 UNIX 系统IPC最古老的形式. 1.特点: 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端.