若是在一个类中想要执行另外一个类中的方法可使用通知
1.建立一个通知对象:使用notificationWithName:object: 或者 notificationWithName:object:userInfo:
html
NSNotification* notification = [NSNotification notificationWithName:kImageNotificationLoadFailed(connection.imageURL)
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:error,@"error",connection.imageURL,@"imageURL",nil]];编程
这 里须要注意的是,建立本身的通知并非必须的。而是在建立本身的通知以前,采用NSNotificationCenter类的方 法 postNotificationName:object: 和 postNotificationName:object:userInfo:更加便利的发出通知。这种状况,通常使用NSNotificationCenter的类方法defaultCenter就得到默认的通知对象,这样你就能够给该程序的默认通知中心发送通知了。注意:每个程序都有一个本身的通知中心,即NSNotificationCenter对象。该对象采用单例设计模式,采用defaultCenter方法就能够得到惟一的NSNotificationCenter对象。设计模式
注意:NSNotification对象是不可变的,由于一旦建立,对象是不能更改的。服务器
2.注册通知:addObserver:selector:name:object:网络
能够看到除了添加观察者以外,还有其接收到通知以后的执行方法入口,即selector的实参。所以为了进行防护式编程,最好先检查观察者是否认义了该方法。例如:添加观察者代码有多线程
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(aWindowBecameMain:)
name:NSWindowDidBecomeMainNotification object:nil];
app
这里保证了self定义了aWindowBecameMain:方法。而对于一个任意的观察者observer,不能保证其对应的selector有aWindowBecameMain:,可采用[observer respondsToSelector:@selector(aWindowBecameMain:)]] 进行检查。因此完整的添加观察者过程为:框架
if([observer respondsToSelector:@selector(aWindowBecameMain:)]) {
[[NSNotificationCenter defaultCenter] addObserver:observer selector:@selector(aWindowBecameMain:) name:NSWindowDidBecomeMainNotification object:nil];
}
异步
注 意到addObserver:selector:name:object:不只指定一个观察者,指定通知中心发送给观察者的消息,还有接收通知的名字,以 及指定的对象。通常来讲不须要指定name和object,但若是仅仅指定了一个object,观察者将收到该对象的全部通知。例如将上面的代码中 name改成nil,那么观察者将接收到object对象的全部消息,可是肯定不了接收这些消息的顺序。若是指指定一个通知名称,观察者将收到它每次发出 的通知。例如,上面的代码中object为nil,那么客户对象(self)将收到任何对象发出NSWindowDidBecomeMainNotification通知。若是既没有指定指定object,也没有指定name,那么该观察者将收到全部对象的全部消息。
分布式
3.发送通知:postNotificationName:object:或者performSelectorOnMainThread:withObject:waitUntilDone:
例如程序能够实现将一个文本能够进行一系列的转换,例如对于一个实例、RTF格式转换成ASCII格式。而转换在一个类(如Converter类)的对象中获得处理,在诚寻执行过程当中能够加入或者删除这种转换。并且当添加或者删除Converter操做时,你的程序可能须要通知其余的对象,可是这些Converter对象并不须要知道被通知对象是什么,能干什么。你只须要声明两个通知,"ConverterAdded" 和 "ConverterRemoved",而且在某一事件发生时就发出这两个通知。
当一个用户安装或者删除一个Converter,它将发送下面的消息给通知中心:
[[NSNotificationCenter defaultCenter]
postNotificationName:@"ConverterAdded" object:self];
或者是
[[NSNotificationCenter defaultCenter]
postNotificationName:@"ConverterRemoved" object:self];
通知中心将会区分它们对象对这些通知感兴趣而且通知他们。若是除了关心观察者的通知名称和观察的对象,还关心其余以外的对象,那么就把以外的对象放在通知的可选字典中,或者用方法postNotificationName:object:userInfo:。
而采用performSelectorOnMainThread:withObject:waitUntilDone:则是直接调用NSNotification的方法postNotification,而postNotificationName和object参数能够放到withObject的实参中。例如:
[[NSNotificationCenter defaultCenter] performSelectorOnMainThread:@selector(postNotification:) withObject:notification waitUntilDone:YES];//注意这里的notification为自定义的一个通知对象,可定义为NSNotification* notification = [NSNotification notificationWithName:@"ConverterAdded"object:self];//那么它的做用与上面的一致
4.移除通知:removeObserver:和removeObserver:name:object:
其中,removeObserver:是删除通知中心保存的调度表一个观察者的全部入口,而removeObserver:name:object:是删除匹配了通知中心保存的调度表中观察者的一个入口。
这个比较简单,直接调用该方法就行。例如:
[[NSNotificationCenter defaultCenter] removeObserver:observer name:nil object:self];
注意参数notificationObserver为要删除的观察者,必定不能置为nil。
PS:这里简单说一下通知中心保存的调度表。通知中心的调度表是给一些观察者指定的一些通知集。一个通知集是通知中心发出的通知的子集。每一个表的入口包含:
通知观察者(必需要的)、通知名称、通知的发送者。
下图表示通知集中指定的通知的调用表入口的四种类型:
下图表示四种观察者的调度表
最后,提醒一下观察者收到通知的顺序是没有定义的。同时通知发出和观察的对象有可能是同样的。通知中心同步转发通知给观察者,就是说 postNotification: 方法直到接收并处理完通知才返回值。要想异步的发送通知,可使用NSNotificationQueue。在多线程编程中,通知通常是在一个发出通知的那个线程中转发,但也多是不在同一个线程中转发通知。
在对象间传递信息的标准方法是消息传递-即一个对象调用另外一个对象的方法。然而,消息传递要求发送消息的对象知道消息的接收者,以及它能够响应什么 消息。这个要求对于委托消息和其它类型的消息是能够的。有些时候,咱们不但愿两个对象之间具备这种紧密的耦合-特别值得注意的缘由是它会把原本独立的子系 统联结在一块儿。并且这种要求也是不切实际的,由于它须要把应用程序中不少全然不一样的对象之间创建硬编码的链接。
对于不能使用标准的消息传递的场合,Cocoa提供了通告广播模型。经过通告机制,一个对象能够通知其它对象本身正在干什么。在这个意义上,通告机 制相似于委托,可是它们之间的区别是很重要的。委托和通告的关键区别在于前者是一对一的通信路径(在向外委托任务的对象和被委托的对象之间)。而通告是潜 在的一对多的通信方式-也就是一种广播。一个对象只能有一个委托,但能够有不少观察者,由于通告的接收者是未知的。对象没必要知道那些观察者是什么对象。任何对象均可以间接地经过通告来观察一个事件,并经过调整本身的外观、行为、和状态来响应事件。通告是一种在应用程序中进行协调和聚合的强大机制。
通告机制是如何工做的?这在概念上至关直接。在进程中有一个称为通告中心的对象,充当通告的信息交换和广播中心。在应用程序的其它地方,须要知道某 个事件的对象在通告中心进行注册,让它知道当该事件发生时,本身但愿获得通知。这种场景的一个例子是当一个弹出式菜单被选择时,控制器对象须要知道这个事 件,以便在用户界面上对反映这个变化。当事件发生时,处理该事件的对象向通告中心发出一个通告,而后通告中心会将它派发给全部的相关的观察者。图5-8描述了这种机制。
请注意:通告中心同步地将通告派发给它的观察者。发出通告的对象直到全部的通告被发出后,才从新得到程序的控制权。若是须要以异步的方式发送通告,必须使用通告队列(参见"通告队列")。通告队列在对特定的通告进行延迟处理,并根据某些具体的条件将相似的通告进行组合以后,才将通告发给通告中心。
任何对象均可以发出通告,也能够在通告中心注册为通告的观察者。发出通告的对象、通告中包含的对象、还有通告的观察者能够是不一样的对象,也能够是同 一个对象(用同一个对象做为通告的发送者和观察者有它的用处,好比用于空闲处理方面)。发送通告的对象不须要知道通告观察者的任何信息。另外一方面,观察者 至少须要知道通告的名称和通告对象中封装的字典的键("通告对象" 部分描述了通告对象的是有什么组成的)。
和委托同样,通告机制是实现应用程序中的对象间通信的好工具。它使应用程序中的对象能够了解其它地方发生的改变。通常地说,一个对象注册为通告的观 察者,是由于它但愿在相应的事件发生后或即将发生时进行调整。举例来讲,若是一个定制视图但愿在窗口调整尺寸的时候改变本身的外观,则能够观察窗口对象发 出的NSWindowDidResizeNotification
通告。通告也容许在对象间传递信息,由于通告中能够包含一个与事件相关的字典。
可是,通告和委托之间是不一样的,这些差异也致使这两种机制应该用于不一样的地方。在早些时候提到,通告模型和委托模型的主要区别在于前者是广播机制,而委托是一对一的关系。每种模型都有本身的优势,通告机制的优势以下:
发出通告的对象不须要知道观察者的标识。
应用程序并不受限于Cocoa框架声明的通告;任何类均可以声明通告,其实例能够发布通告。
通告并不限于应用程序内部的通信;经过分布式通告,一个进程能够将发生的事件通知另外一个进程。
可是,一对一的委托模型也有本身的优势。委托有机会经过将值返回给进行委托的对象来影响事件。另外一方面,通告的观察者必须发挥更为被动的做用,在响应事件时,它只能对自身及其环境产生影响。通告方法必须具备以下形式的签名:
- (void)notificationHandlerName:(NSNotification *); |
这个要求使观察对象没法以任何直接的方式影响原来的事件。可是,委托一般能够影响进行委托的对象对事件的处理方式。并且,Application Kit对象的委托自动注册为其通告的观察者。您只须要实现框架类定义的通告方法,就能够接收通告。
在Cocoa中,通告机制并非观察对象状态变化的惟一选择,在不少状况下甚至都不是最好的选择。Cocoa绑定技术,特别是为其提供支持的键-值 观察(KVO)和键-值绑定(KVB)协议,也可使应用程序中的对象观察其它对象性质的变化。绑定机制比通告机制更为有效。在绑定机制中,被观察对象和 观察对象直接进行通信,不须要像通告中心这样的中间对象。并且,绑定机制不会像常见的通告那样,由于处理无观察者的变化而对性能产生不利影响。
可是在某些场合中,选择通告比选择绑定更为合理。您可能但愿观察事件,而不是对象性质的改变;或者,有些时候遵循KVO和KVB是不适合实际状况的,特别是当须要发送和观察的通告不多的时候。
即便在适合使用通告的场合下,您也应该知道它对性能的潜在影响。通告发出以后,最终会经过本地的通告中心同步地派发给观察对象。无论通告的发送是同 步的仍是异步的,这个过程都是要发生的。若是有不少观察者,或者每一个观察者在处理通告时都作不少工做,您的程序就会有明显的延迟。所以,您应该当心,不要 过分或低效地使用通告。最后,下面这些关于通告用法的原则应该有帮助:
对应用程序应该观察的通告有所选择。
注册通告时,要具体到通告的名称和发送的对象。
尽量高效地实现处理通告的方法。
避免添加或移除不少观察者;经过一些“中间的”观察者将通告的结果传递给它们能够访问的对象要好得多。
通告是一个对象,是NSNotification
的一个实例。该对象封装了与事件有关的信息,好比某个窗口得到了焦点,或者某个网络链接关闭了。当事件发生时,负责处理该事件的对象会向通告中心发出一个通告,通告中心则马上将通告广播给全部注册的对象。
NSNotification
对象中包含一个名称、一个对象、还有一个可选的字典。名称是标识通告的标签;对象是指通告的发送者但愿发给通告观察者的对象(它一般是通告发送者自己),相似于委托消息的发送者对象,通告的接收者能够向该对象查询更多的信息;字典则用于存储与事件有关的信息。
通告中心负责发送和接受通告。它将通告通知给全部符合条件的观察者。通告信息封装在NSNotification
对象中。客户对象在通告中心中将本身注册为特定通告的观察者。当事件发生时,对象向通告中心发出相应的通告。而通告中心会向全部注册过的观察者派发消息,并将通告做为惟一的参数进行传递。通告的发送对象和观察对象有多是同一个对象。
Cocoa框架中包含两种类型的通告中心:
请注意,NSNotificationCenter
和不少其它的Foundation类不一样,不能和其在Core Foundation中对应的结构(CFNotificationCenterRef
)进行自由桥接。
每一个任务都有一个缺省的通告中心,您能够经过NSNotificationCenter
的defaultCenter
类方法来进行访问。通告中心在单任务中处理通告。若是须要在同一个机器的不一样任务之间进行通信,可使用分布式通告中心。
通告中心同步地将通告发送给观察者。换句话说,在发出一个通告时,在全部的观察者接收和处理完成通告以前,程序的控制权不会返回给发送者。若是须要异步发送通告,可使用通告队列,这在"通告队列"部分中进行描述。
在多线程的应用程序中,通告老是在发送的线程中传送,这个线程可能不一样于观察者注册所在的线程。
每一个任务都有一个缺省的分布式通告中心,您能够经过NSDistributedNotificationCenter
的defaultCenter
类方法来访问。这个分布式通告中心负责处理同一个机器的不通任务之间的通告。若是须要实现不一样机器上的任务间通信,请使用分布式对象。
发送分布式通告是一个开销昂贵的操做。通告会被发送给一个系统级别的服务器,而后再分发到注册了该分布式通告的对象所在的任务中。发送通告和通告到达另外一个任务之间的延迟是很大的。事实上,若是发出的通告太多,以至于充满了服务器的队列,就可能被丢弃。
分布式通告经过任务的运行循环来分发。任务必须运行在某种“常见”模式的运行循环下,好比NSDefaultRunLoopMode
模式,才能接收分布式通告。若是接收通告的任务是多线程的,则不要以通告会到达主线程做为前提。通告一般被分发到主线程的运行循环上,可是其它线程也能够接收通告。
尽管常规的通告中心容许任何对象做为通告对象(也就是通告封装的对象),分布式通告中心只支持将NSString
对象做为它的通告对象。因为发出通告的对象和通告的观察者可能位于不一样的任务中,通告不能包含指向任意对象的指针。所以,分布式通告中心要求通告使用字符串做为通告对象。通告的匹配就是基于这个字符串进行的,而不是基于对象指针。
NSNotificationQueue
对象(或者简单称为通告队列)的做用是充当通告中心(NSNotificationCenter
的实例)的缓冲区。通告队列一般以先进先出(FIFO)的顺序维护通告。当一个通告上升到队列的前面时,队列就将它发送给通告中心,通告中心随后将它派发给全部注册为观察者的对象。
每一个线程都有一个缺省的通告队列,与任务的缺省通告中心相关联。图5-9展现了这种关联。您能够建立本身的通告队列,使得每一个线程和通告中心有多个队列。
NSNotificationQueue
类为Foundation Kit的通告机制增长了两个重要的特性:即通告的聚结和异步发送。聚结是把和刚进入队列的通告相相似的其它通告从队列中移除的过程。若是一个新的通告和已 经在队列中的通告相相似,则新的通告不进入队列,而全部相似的通告(除了队列中的第一个通告之外)都被移除。然而,您不该该依赖于这个特殊的聚结行为。
您能够为enqueueNotification:postingStyle:coalesceMask:forModes:
方法的第三个参数指定以下的一或多个常量,指示简化的条件:
NSNotificationNoCoalescing
NSNotificationCoalescingOnName
NSNotificationCoalescingOnSender
您能够对NSNotificationCoalescingOnName
和NSNotificationCoalescingOnSender
常量进行位或操做,指示Cocoa同时使用通告名称和通告对象进行聚结。那样的话,和刚刚进入队列的通告具备相同名称和发送者对象的全部通告都会被聚结。
经过NSNotificationCenter
类的postNotification:
方法及其变体,您能够将通告当即发送给通告中心。可是,这个方法的调用是同步的:即在通告发送对象能够继续执行其所在线程的工做以前,必须等待通告中心将通告派发给全部的观察者并将控制权返回。可是,您也能够经过NSNotificationQueue
的enqueueNotification:postingStyle:
和enqueueNotification:postingStyle:coalesceMask:forModes:
方法将通告放入队列,实现异步发送,在把通告放入队列以后,这些方法会当即将控制权返回给调用对象。
Cocoa根据排队方法中指定的发送风格和运行循环模式来清空通告队列和发送通告。模式参数指定在什么运行循环模式下清空队列。举例来讲,若是您指定NSModalPanelRunLoopMode
模式,则通告只有当运行循环处于该模式下才会被发送。若是当前运行循环不在该模式下,通告就须要等待,直到下次运行循环进入该模式。
向通告队列发送通告能够有三种风格:NSPostASAP
、NSPostWhenIdle
、和NSPostNow
,这些风格将在接下来的部分中进行说明。
以NSPostASAP
风格进入队列的通告会在运行循环的当前迭代完成时被发送给通告中心,若是当前运行循环模式和请求 的模式相匹配的话(若是请求的模式和当前模式不一样,则通告在进入请求的模式时被发出)。因为运行循环在每一个迭代过程当中可能进行多个调用分支 (callout),因此在当前调用分支退出及控制权返回运行循环时,通告可能被分发,也可能不被分发。其它的调用分支可能先发生,好比定时器或由其它源 触发了事件,或者其它异步的通告被分发了。
您一般能够将NSPostASAP
风格用于开销昂贵的资源,好比显示服务器。若是在运行循环的一个调用分支过程当中有不少客户代码在窗口缓冲区中进行描画,在每次描画以后将缓冲区的内容刷新到显示服务器的开销是很昂贵的。在这种状况下,每一个draw...
方法都会将诸如“FlushTheServer” 这样的通告排入队列,并指定按名称和对象进行聚结,以及使用NSPostASAP
风格。结果,在运行循环的最后,那些通告中只有一个被派发,而窗口缓冲区也只被刷新一次。
以NSPostWhenIdle
风格进入队列的通告只在运行循环处于等待状态时才被发出。在这种状态下,运行循环的输入通道中没有任何事件,包括定时器和异步事件。以NSPostWhenIdle
风 格进入队列的一个典型的例子是当用户键入文本、而程序的其它地方须要显示文本字节长度的时候。在用户输入每个字符后都对文本输入框的尺寸进行更新的开销 是很大的(并且不是特别有用),特别是当用户快速输入的时候。在这种状况下,Cocoa会在每一个字符键入以后,将诸如 “ChangeTheDisplayedSize”这样的通告进行排队,同时把聚结开关打开,并使用NSPostWhenIdle
风 格。当用户中止输入的时候,队列中只有一个“ChangeTheDisplayedSize”通告(因为聚结的缘由)会在运行循环进入等待状态时被发出, 显示部分也所以被刷新。请注意,运行循环即将退出(当全部的输入通道都过期的时候,会发生这种状况)时并不处于等待状态,所以也不会发出通告。
以NSPostNow
风格进入队列的通告会在聚结以后,当即发送到通告中心。您能够在不须要异步调用行为的时候 使用NSPostNow
风格(或者经过NSNotificationCenter的postNotification:
方法来发送)。在不少编程环境下,咱们不只容许同步的行为,并且但愿使用这种行为:即您但愿通告中心在通告派发以后返回,以便肯定观察者对象收到通告并进行了处理。固然,当您但愿经过聚结移除队列中相似的通告时,应该用enqueueNotification
...方法,且使用NSPostNow
风格,而不是使用postNotification:
方法。