NSNotification(持续更新)

342 阅读7分钟

介绍

NSNotification是iOS中一个调度消息通知的类,采用单例模式设计,在程序中实现传值、回调等 NSNotification是使用观察者模式来实现的,用于跨层传递消息,也常常用来实现解藕

数据结构

NSNotification包含了一些用于向其他对象发送通知的必要信息,通过NSNotificationCenter发送通知

NSNotification的主要字段如下 (NSNotification是一个不可变对象)

// 通知的名称,用于通知的唯一标识符
@property (readonly, copy) NSNotificationName name;
// 保存发送通知的对象
@property (nullable, readonly, retain) id object;
// 保存给通知接受者传递的额外信息
@property (nullable, readonly, copy) NSDictionary *userInfo;

创建方式

+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject;
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

可以使用上面两种方式,但是我们一般不会这样直接创建,而是通过使用NSNotificationCenter的方式发出通知,在方法内部会直接创建这个对象

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

注意不能通过init方式实例化,因为NSNotification是一个类簇

NSNotificationCenter

NSNotificationCenter提供了一套机制来发送通知,包括下面几种方法

  1. 添加通知观察者 简短来看就是addObserverForName:object:queue:usingBlock:addObserver:selector:name:object:两种方式
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
    // The return value is retained by the system, and should be held onto by the caller in
    // order to remove the observer with removeObserver: later, to stop observation.
  1. 移除通知观察者
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;
  1. 发出通知
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

相关参数解释

  • object:表示的是观察者只会接受来自object对象发出的所注册的通知,而不会接受其他对象发送的所注册的通知
  • 方法addObserverForName:object:queue:usingBlock:
    • queue:block提交到哪个队列区执行
    • block:执行的任务

NSNotificationQueue

NSNotificationQueue在NSNotificationCenter起到了一个缓冲的作用,尽管NSNotificationCenter已经分发通知,但是放入队列的通知可能会延迟,直到当前的runloop结束或runloop处于空闲状态才发送

如果有多个相同的通知,可以在NSNotificationQueue进行合并,这样只会发送一个通知

NSNotificationQueue通过先进先出的方式维护NSNotification的实例,当通知实例位于队列首部时,通知队列会将它发送到通知中心,然后依次向注册的所有观察者派发通知

每个线程有一个默认的和默认通知中心相关联的通知队列defaultQueue

image.png

还有相关的两个NSPostingStyleNSNotificationCoalescing

// NSPostingStyle :用于配置通知什么时候发送
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1, // 当Runloop处于空闲时发出通知
    NSPostASAP = 2,     // 在当前通知调用或计时器结束时发出通知
    NSPostNow = 3       // 在合并通知完成之后立即发出通知
};
// NSNotificationCoalescing :用于配置如何合并通知 
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
    NSNotificationNoCoalescing = 0,        // 不合并通知
    NSNotificationCoalescingOnName = 1,    // 按照通知名字合并通知
    NSNotificationCoalescingOnSender = 2   // 按照传入的object合并通知
};

通知的实现原理

image.png

总的来说NSNotificationCenter中定义两个表,分别是 namelessnamed

typedef struct NCTbl {
  Observation   *wildcard;  /* 保存既没有没有传入通知名字也没有传入object的通知*/
 MapTable       nameless;   /*保存没有传入通知名字的通知 */
 MapTable       named;      /*保存传入了通知名字的通知 */
} NCTable;

同时为了封装观察者信息,也定义了一个Observation保存观察者信息,结构体简化如下

typedef struct  Obs {
  id        observer;   /* 保存接受消息的对象*/
  SEL       selector;   /* 保存注册通知时传入的SEL*/
  struct Obs    *next;  /* 保存注册了同一个通知的下一个观察者*/
  struct NCTbl  *link;  /* 保存改Observation的Table*/
} Observation;

MapTable

named表

image.png

在Named Table中,表的Key是NotifcationName,因为我们在注册观察者的时候是可以传入一个参数object用于只监听指定该对象发出的通知,并且一个通知可以添加多个观察者,所以必须要一张表来保存object和observer的关系

那么如何保存多个观察者的情况呢? 用链表就可以了

(是不是有点混乱?) 来重新梳理一下

  • 最外层一个Table,以通知的name作为key,其value也是一个table(嵌套table)
  • 为了实现只监听指定该对象(object)发出的通知 和 一个通知可以有多个观察者
    • 在内层的Table以传入的object为key ,value是链表的头节点指针
    • 链表中保存所有的观察者

对于object传入nil的情况,这时候其实系统会根据nil产生一个key(可以理解为nil_key),相当于这个key中保存的value(链表)就是没有传入object的所有观察者,当通知被发送时,所有在该链表中的观察者都会收到通知

nameless表

结构比Named Table简单,没有嵌套了

image.png

因为没有name,所以直接用object作为key

对于既没有传递name,也没有传入object的情况,所有的系统通知都会发送到注册的对象这里,也就是上面的wildcard字段

添加观察者的流程

首先在初始化NSNotificationCenter时会创建一个对象,这个对象里保存了NamedTable和NamelessTable和一些其他信息

所有的添加通知操作最后都会调用到addObserver: selector: name: object:

  1. 根据传入的参数,实例化一个Observation,其中保存了观察者对象和下一个Observation的地址 (链表结构)
  2. 根据是否传入name参数选择是在NamedTable还是NamelessTable中操作,假设有传入name
    • 通过name去查找是否有对应的value(此时的value是内层的table)
    • 如果没有对应的value,就创建一个新的table,然后将这个table以name为key添加到NamedTable中
    • 如果有对应的value,直接取出table
  3. 拿到table后,再根据传入的object参数取对应的链表
    • 如果没有找到object(key)对应的Observer(value),那么就创建节点作为链表的头节点
    • 如果找到了就直接在链表末尾插入之前实例化好的Observation对象

对于没有传入name的情况和上面过程类似,只不过是直接根据object去查找对应的链表 对于既没有传入name也没有传入object的,该Observation会添加到wildcard链表中

发送通知的流程

发送通知一般是调用postNotificationName:(NSNotificationName)aName object:(nullable id)anObject来实现

该方法内部会实例化一个NSNotification保存传入的各种参数

发送通知的流程总体来说就是根据NSNotificationName查找到对应的Observer链表,然后遍历整个链表,给每个Observer节点中保存对象和SEL,来向对象发送信息(就是调用对象的SEL方法)

  1. 首先会定义一个数组ObserversArray来保存需要通知的Observer (对于wildcard中的,会遍历并将wildcard中的Observer加到数组的ObserversArray)
  2. 找到以object为key的Observer链表,找到后也是遍历链表并将所有Observer添加到ObserversArray中

至此所有关于NotifcationName的Observer(wildcard+NamedTable+NamelessTable)已经加入到数组ObserversArray

  1. 遍历ObserversArray数组,一次取出其中的Observer节点,最终的调用形式如下
 [observerNode->observer performSelector: o->selector withObject: notification];

发送通知和接收通知的线程是同一个线程,对于需要在主线程中进行的,可以在接收通知的方法中通过线程通信的方式放到主线程中处理

参考博客:深入理解iOS NSNotification