iOS9以后不再需要移除观察者,实现原理

作者:新葡京简介

(通知愈多线程的关系) runloop相关的 通知中心 概念解析 在按钮按下时传值,

NSNotification顾名思义就是通知的作用,一个对象通知另外一个对象,可以用来传递参数、通信等作用,与delegate的一对一不同,通知是一对多的。在一个对象中注册了通知,那么其他任意对象都可以来对这个对象发出通知。这篇文章主要讲诉两个对象

在iOS9
中调整了NSNotificatinonCenter

我们在开发程序的时候,程序内不同对象间的通信是不可避免的,iOS中主要有以下这些通信方式:

- buttonClick:sender{ //添加字典,将label的值通过key值设置传递 NSDictionary *dict=[[NSDictionary alloc]initWithObjectsAndKeys:self.textFieldOne.text,@"textOne",self.textFieldTwo.text,@"textTwo",nil]; //创建通知 NSNotification*notification=[NSNotificationnotificationWithName:@"tongzhi"object:niluserInfo:dict]; //通过通知中心发送通知 [[NSNotificationCenterdefaultCenter] postNotification:notification]; [self.navigationControllerpopViewControllerAnimated:YES]; }
  • NSNotificationCenter
  • NSNotificationQueue

图片 1

图片 2

在发送通知后,在所要接收的控制器中注册通知监听者,将通知发送的信息接收

1、NSNotificationCenter

WechatIMG455.jpeg

iOS中的通信方式

- viewDidLoad{ [superviewDidLoad]; //注册通知 [[NSNotificationCenterdefaultCenter]addObserver:self selector:@selector name:@"tongzhi" object:nil]; }- tongzhi:(NSNotification*)text{ NSLog(@"%@",text.userInfo[@"textOne"]); NSLog(@"-----接收到通知------"); }
 /** * 注册一个通知 * */ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getNotification:) name:@"notificationMethon" object:nil];

iOS9
开始不需要在观察者对象释放之前从通知中心移除观察者了。但是如果使用-[NSNotificationCenter addObserverForName:object:queue:usingBlock:]
方法还是需要手动释放。因为NSNotificationCenter
依旧对它们强引用。# NSNotificationQueueNSNotificationQueue
通知队列,用来管理多个通知的调用。通知队列通常以先进先出(FIFO)顺序维护通。NSNotificationQueue
就像一个缓冲池把一个个通知放进池子中,使用特定方式通过NSNotificationCenter
发送到相应的观察者。下面我们会提到特定的方式即合并通知和异步通知。
创建通知队列方法:- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter NS_DESIGNATED_INITIALIZER;

图中按照耦合度的强弱和通信的形式(一对一还是一对多)进行了划分,这篇文章我们主要说一下Notifications。

移除通知:removeObserver:和removeObserver:name:object:其中,removeObserver:是删除通知中心保存的调度表一个观察者的所有入口,而removeObserver:name:object:是删除匹配了通知中心保存的调度表中观察者的一个入口。这个比较简单,直接调用该方法就行。例如:

NSNotificationCenter是一个单列,我们可以通过defaultCenter来获取到通知中心这个单列,使用通知的第一步就是添加通知。

往队列加入通知方法:- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSRunLoopMode> *)modes;

通知机制想必大家都很熟悉,平常的开发中或多或少的应该都用过。它是Cocoa中一个非常重要的机制,能把一个事件发送给多个监听该事件的对象,而消息的发送者不需要知道消息接收者的任何信息,消息的接受者也只是向通知中心(NSNotificationCenter)注册监听自己感兴趣的通知即可,任何对象都可以监听或者发送通知,这在很大程度上降低了消息发送者和接受者之间的耦合度。这也是iOS中观察者模式的一种实现方式。(关于观察者模式的另一种实现方式,可以看看我的这一篇文章:谈谈 KVO)

[[NSNotificationCenter defaultCenter] removeObserver:observername:nil object:self];
 /** * 移除所有通知 */ [[NSNotificationCenter defaultCenter] removeObserver:self]; /** * 移除单个通知 * */ [[NSNotificationCenter defaultCenter] removeObserver:self name:@"notificationMethon" object:nil];

移除队列中的通知方法:- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask;

Notification(通知)

当我们发通知时,我们发送的是什么?答案是Notification,一个Notification对象封装了通知发送者想要传递给监听的的信息,它有3个属性:

@property (readonly, copy) NSString *name;
@property (nullable, readonly, retain) id object;
@property (nullable, readonly, copy) NSDictionary *userInfo;

name:通知的名称,用来标示一个通知,一般为字符串
object:任意想要携带的对象,通常为发送者自己
userInfo:附加信息

通知就是以Notification的形式从通知发送者发出,到通知中心,然后再分发给所有监听该通知的对象的,通知监听者们接收到通知之后,可以获取到传递过来的Notification对象,从而获取里面封装的一些信息,做相应的处理。

注意参数notificationObserver为要删除的观察者,一定不能置为nil。

需要注意的是当我们添加一个通知以后,必须在合适的位置将通知移除,不然下次再添加这个通知并调用时,这个通知将会被调用多次,而这一般不是我们所预料的。一般我们在对象的析构函数中将通知移除,我们可以选择将这个对象中的所有通知移除,也可以选择一个一个按照通知的name来移除。

发送方式NSPostingStyle
包括三种类型:typedef NS_ENUM(NSUInteger, NSPostingStyle) {NSPostWhenIdle = 1,NSPostASAP = 2,NSPostNow = 3};

NSNotificationCenter(通知中心)

通知中心是整个通知机制的关键所在,它管理着监听者的注册和注销,通知的发送和接收。通知中心维护着一个通知的分发表,把所有发送者发送的通知,转发给对应的监听者们。每一个iOS程序都有一个唯一的通知中心,你不必自己去创建一个,它是一个单例,通过[NSNotificationCenter defaultCenter]方法获取。

注册监听者方法:

- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;
- (id <NSObject>)addObserverForName:(nullable NSString *)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;

第一个方法是大家常用的方法,不用多说,第二个方法带了一个block,这个block就是通知被触发时要执行的block,这个block带有一个notification参数;该方法还有一个queue参数,可以指定这个block在哪个队列上执行,如果传nil,这个block将会在发送通知的线程中同步执行。然后注意到,这个方法有一个id类型的返回值,这个返回值是一个不透明的中间值,用来充当监听者,使用时,我们需要将这个返回的监听者保存起来,在后面移除监听者的时候用到。

移除监听者方法:

- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSString *)aName object:(nullable id)anObject;

在监听对象销毁前,记得把该对象监听的通知移除掉。

发送通知方法:

- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

还有2点需要注意的是:

  • 通知中心默认是以同步的方式发送通知的,也就是说,当一个对象发送了一个通知,只有当该通知的所有接受者都接受到了通知中心分发的通知消息并且处理完成后,发送通知的对象才能继续执行接下来的方法。异步发送通知的方法下面会说到。
  • 在一个多线程的程序中,发送方发送通知的线程通常就是监听者接受通知的线程,这可能和监听者注册时的线程不一样。

Cocoa中有2中通知中心,一种是NSNotificationCenter,它只能处理一个程序内部的通知,另一种是NSDistributedNotificationCenter(mas OS上才有),它能处理一台机器上不同程序之间的通知,由于本篇主要讲iOS的通知,所以在这就不赘述,感兴趣的同学可以去苹果官方文档里详细了解。

从使用的角度上考虑 通知中心属于十分简单的我们从三个角度来理解 通知中心, 原理,多线程角度,内存管理角度等三方面深入理解他.

 /** * 发出通知 */ [[NSNotificationCenter defaultCenter] postNotificationName:@"notificationMethon" object:nil userInfo:@{@"key":@"value"}];

NSPostWhenIdle:空闲发送通知 当运行循环处于等待或空闲状态时,发送通知,对于不重要的通知可以使用。NSPostASAP:尽快发送通知 当前运行循环迭代完成时,通知将会被发送,有点类似没有延迟的定时器。NSPostNow :同步发送通知 如果不使用合并通知 和postNotification:
一样是同步通知。
合并通知NSNotificationCoalescing
也包括三种类型:typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {NSNotificationNoCoalescing = 0,NSNotificationCoalescingOnName = 1,NSNotificationCoalescingOnSender = 2};

NSNotificationQueue(通知队列)

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

图片 3

通知队列

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

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

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

1.实现原理,

当我们添加通知以后,我们就可以发出通知,也是通过NSNotificationCenter类的defaultCenter来获取到通知中心,然后通过postNotificationName来发出通知。这里需要注意的一点就是这样发出的通知是同步操作,也就是只有当发出的通知执行完毕以后才会继续执行接下去的代码。

NSNotificationNoCoalescing:不合并通知。NSNotificationCoalescingOnName:合并相同名称的通知。NSNotificationCoalescingOnSender:合并相同通知和同一对象的通知。
通过合并我们可以用来保证相同的通知只被发送一次。forModes:(nullable NSArray<NSRunLoopMode> *)modes
可以使用不同的NSRunLoopMode
配合来发送通知,可以看出实际上NSNotificationQueue
与RunLoop
的机制以及运行循环有关系,通过NSNotificationQueue
队列来发送的通知和关联的RunLoop
运行机制来进行的。

通知合并

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

你可能会这样做,设置一个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];

我们可以看到 通知中心里面是两个类 NSNotificationCenter NSNotification 而 NSNotificationCenter 是用来统一管理整体的通知消息

-getNotification:(NSNotification *)info{ NSDictionary *dict = info.userInfo;}
异步发送通知

使用通知队列的下面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:方法。

使用时 还是把 NSNotificationCenter 以单利的形式创建 通过 center post 通知通过center addObserver:observer selector:Selector name:(NSString *)Name object:Object 建立监听,通知中心的优点在于 一处发送,全局接受.那么也就意味着 转发和监听同步的addObserver:observer selector:Selector name:(NSString *)Name object:Object 里面做了将 name Selector Object 赋值给一个model 并且每注册一个监听,就会给一个model 赋值 并且 model添加到一个可变数组中

这是发出通知以后会调用的方法,在多线程操作时,发出通知的对象和接收通知的对象处于同一个线程。在这个响应的方法中有一个参数<code>(NSNotification *)info</code>这个参数是NSNotification类型的,这个类型有几个属性:

发送通知到指定线程

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

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

不过这个处理思路也是有一些局限性的,苹果官方文档中给出了一些基本代码和思路,感兴趣的同学可以看看。

当我们post 通知是 我们就去便利这个可变数组,并且取得数组中的model 这时候我们就可以拿到 通知的name,去执行通知的对象 ,以及执行的通知的方法,[对象 performSelector:selector withObject:notification];那么就好啦 这时候相应的对象就去只想这个selector啦并且将参数 notification传递过去啦还有就是 remove 就是从数组中移除就好一个通正中心简单的原理就是这样,当然还有更加会有多线程一会解释

@property (readonly, copy) NSString *name;@property (nullable, readonly, retain) id object;@property (nullable, readonly, copy) NSDictionary *userInfo;
Reference
  • NSHipster
  • iOS Notification Center
  • 苹果官方文档

2.多线程的角度考虑读官方文档时多次提到 通知是在那个线程发送的就在那个线程接受.也就是会涉及到一个问题就是我实在次级线程post 然后想在其他线程接受那该怎么办官网文档给的方法就是重定向代码如下:

其中<code> userInfo </code>这个属性是一个字典,也是最为关键以及有用的属性,我们通过这个属性来传递参数等一些有用的信息。

@interface ViewController () @property (nonatomic) NSMutableArray *notifications; // 通知队列@property (nonatomic) NSThread *notificationThread; // 期望线程@property (nonatomic) NSLock *notificationLock; // 用于对通知队列加锁的锁对象,避免线程冲突@property (nonatomic) NSMachPort *notificationPort; // 用于向期望线程发送信号的通信端口 @end @implementation ViewController - viewDidLoad { [super viewDidLoad]; NSLog(@"current thread = %@", [NSThread currentThread]); // 初始化 self.notifications = [[NSMutableArray alloc] init]; self.notificationLock = [[NSLock alloc] init]; self.notificationThread = [NSThread currentThread]; self.notificationPort = [[NSMachPort alloc] init]; self.notificationPort.delegate = self; // 往当前线程的run loop添加端口源 // 当Mach消息到达而接收线程的run loop没有运行时,则内核会保存这条消息,直到下一次进入run loop [[NSRunLoop currentRunLoop] addPort:self.notificationPort forMode:(__bridge NSString *)kCFRunLoopCommonModes]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"TestNotification" object:nil]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil userInfo:nil]; });} - handleMachMessage:msg { [self.notificationLock lock]; while ([self.notifications count]) { NSNotification *notification = [self.notifications objectAtIndex:0]; [self.notifications removeObjectAtIndex:0]; [self.notificationLock unlock]; [self processNotification:notification]; [self.notificationLock lock]; }; [self.notificationLock unlock];} - processNotification:(NSNotification *)notification { if ([NSThread currentThread] != _notificationThread) { // Forward the notification to the correct thread. [self.notificationLock lock]; [self.notifications addObject:notification]; [self.notificationLock unlock]; [self.notificationPort sendBeforeDate:[NSDate date] components:nil from:nil reserved:0]; } else { // Process the notification here; NSLog(@"current thread = %@", [NSThread currentThread]); NSLog(@"process notification"); }} @end

总结以上内容,所以通知的最简单的使用过程就是:1、添加通知;2、发出通知;3、移除通知;

上面的方法是将子线程通知到主线程中去执行任务当然如果想在某个子线称重执行那么稍微修改一下就搞定啦

2、NSNotificationQueue

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ self.notificationThread = [NSThread currentThread]; NSLog(@"指定current thread = %@", self.notificationThread); self.notificationPort = [[NSMachPort alloc] init]; self.notificationPort.delegate = self; [[NSRunLoop currentRunLoop] addPort:self.notificationPort forMode:(__bridge NSString *)kCFRunLoopCommonModes]; [[NSRunLoop currentRunLoop] run]; });创建子线程并且为其开启runloop,这样就搞定啦可以再指定的子线程中去执行通知

上面说到<code> NSNotificationCenter</code>是一个同步操作,也就是只有当响应的通知的代码执行完毕以后,发出通知的对象的代码才会继续往下执行。那么<code> NSNotificationQueue</code>就有一些区别,他有两个非常重要的特点:即通告的聚结和异步发送。聚结是把和刚进入队列的通告相类似的其它通告从队列中移除的过程。如果一个新的通告和已经在队列中的通告相类似,则新的通告不进入队列,而所有类似的通告(除了队列中的第一个通告以外)都被移除。然而,您不应该依赖于这个特殊的聚结行为。而异步发送则很好理解了,也就是说发出通知以后立刻返回,也就是是继续执行下面的代码,并不管通知发出后的具体情况

通告队列

/** 发出通知 */ NSNotification *notifacation = [[NSNotification alloc]initWithName:@"notificationMethon" object:nil userInfo:@{@"key":@"value1"}]; [[NSNotificationQueue defaultQueue] enqueueNotification:notifacation postingStyle:NSPostWhenIdle];

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

我们可以通过,NSNotificationQueue的defaultQueue来获取到这个通知队列,然后调用<code>enqueueNotification</code>来发出通知,我们可以看到第二个参数<code>postingStyle</code>,这个参数是一个枚举,他可以是以下三个值:

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

typedef NS_ENUM(NSUInteger, NSPostingStyle) { NSPostWhenIdle = 1, NSPostASAP = 2, NSPostNow = 3};

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

这三个不同的值是有一定区别的。(以下内容摘抄自网络)

聚结的通告

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

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

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

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

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

NSNotificationNoCoalescing

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

NSNotificationCoalescingOnName

如果对您有一定帮助,请关注本人。(转载请表明出处、作者)

NSNotificationCoalescingOnSender

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

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

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

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

尽快发送

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

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

空闲时发送

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

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

发送通知时,这么写 创建notification 然后加入队列 并且选择发送模式 NSNotification *notifacation = [[NSNotification alloc]initWithName:@"qqqq" object:nil userInfo:nil]; [[NSNotificationQueue defaultQueue] enqueueNotification:notifacation postingStyle:3];

合并通知一些情况下,某一事件一旦发生,开发者可能只想要至少发送一次通知,即使该事件可能频繁多次发生。此时即可采用合并通知,合并是把和刚进入队列的通知相类似的其它通知从队列中移除的过程。如果一个新的通知和已经在队列中的通知相类似,则新的通知不进入队列,而所有类似的通知(除了队列中的第一个通知以外)都被移除。**但是,为安全起见,开发者不应该完全依赖于这个特殊的合并行为。我想说说 合并并没有用过

3.从内存管理的角度考虑使用通知中心时,有事会报错:thread1:exc_bad)access(code=exc_1386_gpflt)这个错误的意思就是你想一个已经释放的对象发送了消息那么我们回顾一下 通话中中心的使用啊当我们 注册一个通知中心 并且对其进行监听时 我们会 把自身的类 也就是self 还有通知的名字 已经处理通知结果的的方法,放入一个model中并将model添加到一个可变数组中.当我们去post通知时,从数组中遍历 model 然后取出model中的值 进行[对象 performSelector:selector withObject:notification];然而 如果监听通知的类这是已经被释放 那么 这时再去post 那么就会想一个已经释放了的对象进行发送消息那么 自然而然的就会报错.那么解决这种错误的方法就是移除通知.那么为什么移除通知就不会再崩溃了呢首先我们要理解remove的原理,前面我们知道 监听时会给model赋值 然后将model添加可变数组中,然后post时 便利数组取出model.根据model 去[对象 performSelector:selector withObject:notification];发送消息.那么如果我们在可变数组中直接移除了某个已经释放对象的model,那么post时 就不会遍历到相应的model,也不会[对象 performSelector:selector withObject:notification];发送消息. 那么自然就不会发送消息崩溃如果 我们在vc1 中post 通知 在vc2中监听通知我们做这样一个通知 从vc1 跳转到vc2 这是vc2 进行了注册和监听,这是我们pop返回vc1 这是vc2 已经释放,这时 我们在vc1中点击post 发送通知,你会发现不科学,因为没有抛出异常.难道是理论出了错误. 这是我们队 center创建一个分类,然后重新remove方法,(这里其实已经覆盖了原方法),那么在remove方法中进行打印,再重复刚才的步骤,发现分类执行了remove并且打印了 同时也抛出了异常这说明,系统自动帮我们执行了 remove

好了 就到这了吧 各位道友有啥建议或者想法 欢迎评论

本文由新葡京8455发布,转载请注明来源

关键词: