iOS Notification Center

作者: MasonFu | 来源:发表于2015-05-21 16:38 被阅读9060次

    Notification(通知)是iOS系统下重要的消息传递机制之一,通知封装了诸如窗口获得焦点、网络连接关闭等事件信息,通知的内容可按照我们实际的需求来定制。在实际开发中或多或少都会接触到,通过官方文档,我总结了下。

    概览

    在对象间进行信息传递的标准方式就是消息(一个对象调起另一对象的方法)。这就要求发送消息的对象必须知道接收者是谁,消息的响应者是谁。在iOS系统下,通过广播机制来达到这个目的,对象向通知中心(NSNotificationCenter)投递一个通知,由通知中心将其收到的通知分发给“感兴趣”的接收者。通知中心的原理非常符合设计模式中的观察者(Observer)模式。

    NotificationCenter

    NSNotificationCenter 与 Delegate 类似,都属于对象之间通信的手段,但也存在不同,开发者需要根据具体应用场景选择恰当的通信方式。不同点在于:

    1. Delegate通常是对象一对一的通信,而通知可以是一对多个对象,并且不再需要返回值。
    2. 对象可以从通知中心中接收任意个它感兴趣的通知,而不像代理只能接收预设的方法。
    3. 发送通知的对象甚至不知道(也许不想知道)通知消息响应者们(Observers)的存在。

    通知

    通知NSNotification结构中主要包括:

    @property (readonly, copy) NSString *name;  // 通知的标识名称(一般为常量字符串)
    @property (readonly, retain) id object;  // 任意想要携带的对象,通常为发送者自己
    @property (readonly, copy) NSDictionary *userInfo; // 关于通知的附加信息
    

    通知中心

    Cocoa框架中包括两种通知中心:

    NSNotificationCenter 单进程通知管理
    NSDistributedNotificationCenter 一台机器中多个进程的通知管理

    NSNotificationCenter

    每个进程都包含一个默认的通知中心,通过[NSNotificationCenter defaultCenter]获取,需要注意的是:

    1.通知中心分发给观察者处理采用同步机制,也就是说,当某一对象发送一个通知时,会一直阻塞在发送方法内,直到通知中心将该通知分发给所有观察者并且全部成功处理返回后,发送者才能执行其所在线程内的后续代码。如果需要异步发送通知,可以使用文章后面的“通知队列”。
    2.在多线程的应用程序中,通知总是在发送的线程中传送,这个线程可能不同于观察者注册所在的线程。

    发送通知方法:
    - (void)postNotification:(NSNotification *)notification;
    - (void)postNotificationName:(NSString *)aName object:(id)anObject;
    - (void)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;

    注册通知方法:
    - (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject;
    - (void)removeObserver:(id)observer;
    - (void)removeObserver:(id)observer name:(NSString *)aName object:(id)anObject;

    NSDistributedNotificationCenter

    通知队列

    通知队列(Notification queue)更像是通知中心的缓冲区,通知队列通常以先进先出(FIFO)的顺序管理通知。当一个通知上升到队列的前面时,队列就将它发送给通知中心,通知中心随后将它派发给所有注册为观察者的对象。

    NotificationQueue

    每个线程都有一个缺省的通知队列[NSNotificationQueue defaultQueue],与进程的缺省通知中心相关联。开发者可以自己创建通知队列,并且每个通知中心和线程都可以有多个队列。

    向队列投递一个异步通知方法如下:
    - (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
    - (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSUInteger)coalesceMask forModes:(NSArray *)modes;

    NSNotificationQueue类为Foundation Kit的通知机制增加了两个重要的特性:合并通知和异步发送

    异步发送

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

    1.尽快发送

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

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

    2.空闲时发送

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

    3.立即发送

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

    合并通知

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

    分发通知到指定线程

    一般通知中心只在投递所发生的线程内分发通知,分布式通知中心在主线程内分发通知。但是有时候,开发者可能需要将通知放在指定线程内以自己的通知中心来分发处理,例如一个对象运行在后台线程里并监听像窗口关闭这个的UI触发事件时,开发者可能想要在后台线程里接收该通知而非主线程,这种情况下,开发者需要捕获到在默认线程中传递的通知,并且将该通知重定向到恰当的线程中去。

    重定向通知方法是,通过使用定制的通知队列(不是NSNotificationQueue)在非目标线程中捕获所有通知并且将它们重新投递给正确的线程。流程如下,开发者先注册一个通知,当通知到达时,验证其是否应该在当前线程中被处理,如果不是,需要将此通知存入队列中,并且发送一个信号给目标线程以示有新通知需要处理,目标线程收到信号后,从该通知队列中取出并且处理通知。

    下面是官方文档给出的基本思路

    观察者对象需要实现的变量及delegate

    @interface MyThreadedClass: NSObject<NSMachPortDelegate>
    /* Threaded notification support. */
    @property NSMutableArray *notifications;
    @property NSThread *notificationThread;
    @property NSLock *notificationLock;
    @property NSMachPort *notificationPort;
    - (void) setUpThreadingSupport;
    - (void) processNotification:(NSNotification *)notification;
    @end
    

    注册通知之前先初始化这些变量

    - (void) setUpThreadingSupport {
    if (self.notifications) {
            return; 
     }
        self.notifications      = [[NSMutableArray alloc] init];
        self.notificationLock   = [[NSLock alloc] init];
        self.notificationThread = [NSThread currentThread];
        self.notificationPort = [[NSMachPort alloc] init];
        [self.notificationPort setDelegate:self];
        [[NSRunLoop currentRunLoop] addPort:self.notificationPort
    }
    

    在NSMacPortDelegate回调中,对线程中捕获到的消息进行处理

      - (void) handleMachMessage:(void *)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];
    }
    

    检测消息是否应该在此线程被处理,如果是,正常处理,否则将其入队并触发信号

    - (void)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;
        }
    }
    

    最后,在真正需要处理通知的目标线程里注册通知,注册之前需要先调用setupThreadingSupport以初始化通知属性,然后像一般注册一样指定通知响应方法即可。

    [self setupThreadingSupport];
    [[NSNotificationCenter defaultCenter]
        addObserver:self
        selector:@selector(processNotification:)
        name:@"NotificationName"
        object:nil];
    

    相关文章

      网友评论

      • roylly:分发通知到指定线程,有什么具体应用场景吗?我一般用到的是在非主线程做网络请求等,获得数据后,把数据放入notification中一起发出,此时接收到通知后,再异步getMainQueue,做UI更新之类的。似乎从来没有用到过,需要发送通知到指定的线程,当然app中直接开线程的操作很少用,都是开队列。
      • 张自:感谢分享
      • lesmiserables0:不错,总结的很好,谢谢分享~!
        1024

      本文标题:iOS Notification Center

      本文链接:https://www.haomeiwen.com/subject/vzwlqttx.html