iOS NSNotification的使用

 

如果在一个类中想要执行另一个类中的方法可以使用通知
1.创建一个通知对象:
使用notificationWithName:object: 或者 notificationWithName:object:userInfo:

    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];

这里保证了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描述了这种机制。

 

请注意:通告中心同步地将通告派发给它的观察者。发出通告的对象直到所有的通告被发出后,才重新获得程序的控制权。如果需要以异步的方式发送通告,必须使用通告队列(参见"通告队列")。通告队列在对特定的通告进行延迟处理,并根据某些具体的条件将类似的通告进行组合之后,才将通告发给通告中心。

 

 

 

 

图5-8  公布和广播通告

Posting and broadcasting a notification

 

 

 

任何对象都可以发出通告,也可以在通告中心注册为通告的观察者。发出通告的对象、通告中包含的对象、还有通告的观察者可以是不同的对象,也可以是同 一个对象(用同一个对象作为通告的发送者和观察者有它的用处,比如用于空闲处理方面)。发送通告的对象不需要知道通告观察者的任何信息。另一方面,观察者 至少需要知道通告的名称和通告对象中封装的字典的键("通告对象" 部分描述了通告对象的是有什么组成的)。

 

进一步阅读: 如果需要有关通告机制的完全讨论。

 

 

本部分包含如下主要内容:

 

何时以及如何使用通告
通告对象
通告中心
通告队列

 


 

何时以及如何使用通告

 

和委托一样,通告机制是实现应用程序中的对象间通讯的好工具。它使应用程序中的对象可以了解其它地方发生的改变。一般地说,一个对象注册为通告的观 察者,是因为它希望在相应的事件发生后或即将发生时进行调整。举例来说,如果一个定制视图希望在窗口调整尺寸的时候改变自己的外观,则可以观察窗口对象发 出的NSWindowDidResizeNotification通告。通告也允许在对象间传递信息,因为通告中可以包含一个与事件相关的字典。

 

但是,通告和委托之间是不同的,这些差别也导致这两种机制应该用于不同的地方。在早些时候提到,通告模型和委托模型的主要区别在于前者是广播机制,而委托是一对一的关系。每种模型都有自己的优点,通告机制的优点如下:

 

  • 发出通告的对象不需要知道观察者的标识。

  • 应用程序并不受限于Cocoa框架声明的通告;任何类都可以声明通告,其实例可以发布通告。

  • 通告并不限于应用程序内部的通讯;通过分布式通告,一个进程可以将发生的事件通知另一个进程。

 

但是,一对一的委托模型也有自己的优点。委托有机会通过将值返回给进行委托的对象来影响事件。另一方面,通告的观察者必须发挥更为被动的作用,在响应事件时,它只能对自身及其环境产生影响。通告方法必须具有如下形式的签名:

 

- (void)notificationHandlerName:(NSNotification *);

 

这个要求使观察对象无法以任何直接的方式影响原来的事件。但是,委托通常可以影响进行委托的对象对事件的处理方式。而且,Application Kit对象的委托自动注册为其通告的观察者。您只需要实现框架类定义的通告方法,就可以接收通告。

 

在Cocoa中,通告机制并不是观察对象状态变化的唯一选择,在很多情况下甚至都不是最好的选择。Cocoa绑定技术,特别是为其提供支持的键-值 观察(KVO)和键-值绑定(KVB)协议,也可以使应用程序中的对象观察其它对象性质的变化。绑定机制比通告机制更为有效。在绑定机制中,被观察对象和 观察对象直接进行通讯,不需要像通告中心这样的中间对象。而且,绑定机制不会像常见的通告那样,因为处理无观察者的变化而对性能产生不利影响。

 

但是在某些场合中,选择通告比选择绑定更为合理。您可能希望观察事件,而不是对象性质的改变;或者,有些时候遵循KVO和KVB是不适合实际情况的,特别是当需要发送和观察的通告很少的时候。

 

即使在适合使用通告的场合下,您也应该知道它对性能的潜在影响。通告发出之后,最终会通过本地的通告中心同步地派发给观察对象。不管通告的发送是同 步的还是异步的,这个过程都是要发生的。如果有很多观察者,或者每个观察者在处理通告时都做很多工作,您的程序就会有明显的延迟。因此,您应该小心,不要 过度或低效地使用通告。最后,下面这些关于通告用法的原则应该有帮助:

 

  • 对应用程序应该观察的通告有所选择。

  • 注册通告时,要具体到通告的名称和发送的对象。

  • 尽可能高效地实现处理通告的方法。

  • 避免添加或移除很多观察者;通过一些“中间的”观察者将通告的结果传递给它们可以访问的对象要好得多。

 

进一步阅读:如果您需要有效使用通告的详细信息,请参见Cocoa性能指南中的"通告"部分。

 

 

 

通告对象

 

通告是一个对象,是NSNotification的一个实例。该对象封装了与事件有关的信息,比如某个窗口获得了焦点,或者某个网络连接关闭了。当事件发生时,负责处理该事件的对象会向通告中心发出一个通告,通告中心则立刻将通告广播给所有注册的对象。

 

NSNotification对象中包含一个名称、一个对象、还有一个可选的字典。名称是标识通告的标签;对象是指通告的发送者希望发给通告观察者的对象(它通常是通告发送者本身),类似于委托消息的发送者对象,通告的接收者可以向该对象查询更多的信息;字典则用于存储与事件有关的信息。

 

 

通告中心

 

通告中心负责发送和接受通告。它将通告通知给所有符合条件的观察者。通告信息封装在NSNotification对象中。客户对象在通告中心中将自己注册为特定通告的观察者。当事件发生时,对象向通告中心发出相应的通告。而通告中心会向所有注册过的观察者派发消息,并将通告作为唯一的参数进行传递。通告的发送对象和观察对象有可能是同一个对象。

 

Cocoa框架中包含两种类型的通告中心:

 

  • 通告中心(NSNotificationCenter的实例),管理单任务的通告。

  • 分布式通告中心(NSDistributedNotificationCenter的实例),管理单台计算机上多任务之间的通告。

 

请注意,NSNotificationCenter和很多其它的Foundation类不同,不能和其在Core Foundation中对应的结构(CFNotificationCenterRef)进行自由桥接。

 

 

NSNotificationCenter

 

每个任务都有一个缺省的通告中心,您可以通过NSNotificationCenterdefaultCenter类方法来进行访问。通告中心在单任务中处理通告。如果需要在同一个机器的不同任务之间进行通讯,可以使用分布式通告中心。

 

通告中心同步地将通告发送给观察者。换句话说,在发出一个通告时,在所有的观察者接收和处理完成通告之前,程序的控制权不会返回给发送者。如果需要异步发送通告,可以使用通告队列,这在"通告队列"部分中进行描述。

 

在多线程的应用程序中,通告总是在发送的线程中传送,这个线程可能不同于观察者注册所在的线程。

 

 

NSDistributedNotificationCenter

 

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

 

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

 

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

 

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

 

 

通告队列

 

NSNotificationQueue对象(或者简单称为通告队列)的作用是充当通告中心(NSNotificationCenter的实例)的缓冲区。通告队列通常以先进先出(FIFO)的顺序维护通告。当一个通告上升到队列的前面时,队列就将它发送给通告中心,通告中心随后将它派发给所有注册为观察者的对象。

 

每个线程都有一个缺省的通告队列,与任务的缺省通告中心相关联。图5-9展示了这种关联。您可以创建自己的通告队列,使得每个线程和通告中心有多个队列。

 

 

 

图5-9  通告队列和通告中心

A notification queue and notification center

 


 

聚结的通告

 

NSNotificationQueue类为Foundation Kit的通告机制增加了两个重要的特性:即通告的聚结和异步发送。聚结是把和刚进入队列的通告相类似的其它通告从队列中移除的过程。如果一个新的通告和已 经在队列中的通告相类似,则新的通告不进入队列,而所有类似的通告(除了队列中的第一个通告以外)都被移除。然而,您不应该依赖于这个特殊的聚结行为。

 

您可以为enqueueNotification:postingStyle:coalesceMask:forModes:方法的第三个参数指定如下的一或多个常量,指示简化的条件:

 

  • NSNotificationNoCoalescing

  • NSNotificationCoalescingOnName

  • NSNotificationCoalescingOnSender

 

您可以对NSNotificationCoalescingOnNameNSNotificationCoalescingOnSender常量进行位或操作,指示Cocoa同时使用通告名称和通告对象进行聚结。那样的话,和刚刚进入队列的通告具有相同名称和发送者对象的所有通告都会被聚结。

 

 

异步发送通告

 

通过NSNotificationCenter类的postNotification:方法及其变体,您可以将通告立即发送给通告中心。但是,这个方法的调用是同步的:即在通告发送对象可以继续执行其所在线程的工作之前,必须等待通告中心将通告派发给所有的观察者并将控制权返回。但是,您也可以通过NSNotificationQueueenqueueNotification:postingStyle:enqueueNotification:postingStyle:coalesceMask:forModes:方法将通告放入队列,实现异步发送,在把通告放入队列之后,这些方法会立即将控制权返回给调用对象。

 

Cocoa根据排队方法中指定的发送风格和运行循环模式来清空通告队列和发送通告。模式参数指定在什么运行循环模式下清空队列。举例来说,如果您指定NSModalPanelRunLoopMode模式,则通告只有当运行循环处于该模式下才会被发送。如果当前运行循环不在该模式下,通告就需要等待,直到下次运行循环进入该模式。

 

向通告队列发送通告可以有三种风格:NSPostASAPNSPostWhenIdle、和NSPostNow,这些风格将在接下来的部分中进行说明。

 

 

尽快发送

 

NSPostASAP风格进入队列的通告会在运行循环的当前迭代完成时被发送给通告中心,如果当前运行循环模式和请求 的模式相匹配的话(如果请求的模式和当前模式不同,则通告在进入请求的模式时被发出)。由于运行循环在每个迭代过程中可能进行多个调用分支 (callout),所以在当前调用分支退出及控制权返回运行循环时,通告可能被分发,也可能不被分发。其它的调用分支可能先发生,比如定时器或由其它源 触发了事件,或者其它异步的通告被分发了。

 

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

 

 

空闲时发送

 

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

 

 

立即发送

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

来源:http://www.cnblogs.com/pengyingh/articles/2346301.html

posted on 2014-04-22 21:49  猿人谷  阅读(31578)  评论(1编辑  收藏  举报