介绍
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提供了一套机制来发送通知,包括下面几种方法
- 添加通知观察者
简短来看就是
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.
- 移除通知观察者
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;
- 发出通知
- (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
还有相关的两个NSPostingStyle
和NSNotificationCoalescing
// 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合并通知
};
通知的实现原理
总的来说NSNotificationCenter中定义两个表,分别是 nameless
和 named
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表
在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简单,没有嵌套了
因为没有name,所以直接用object作为key
对于既没有传递name,也没有传入object的情况,所有的系统通知都会发送到注册的对象这里,也就是上面的wildcard字段
添加观察者的流程
首先在初始化NSNotificationCenter时会创建一个对象,这个对象里保存了NamedTable和NamelessTable和一些其他信息
所有的添加通知操作最后都会调用到addObserver: selector: name: object:
- 根据传入的参数,实例化一个Observation,其中保存了观察者对象和下一个Observation的地址 (链表结构)
- 根据是否传入name参数选择是在NamedTable还是NamelessTable中操作,假设有传入name
- 通过name去查找是否有对应的value(此时的value是内层的table)
- 如果没有对应的value,就创建一个新的table,然后将这个table以name为key添加到NamedTable中
- 如果有对应的value,直接取出table
- 拿到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方法)
- 首先会定义一个数组ObserversArray来保存需要通知的Observer (对于wildcard中的,会遍历并将wildcard中的Observer加到数组的ObserversArray)
- 找到以object为key的Observer链表,找到后也是遍历链表并将所有Observer添加到ObserversArray中
至此所有关于NotifcationName的Observer(wildcard+NamedTable+NamelessTable)已经加入到数组ObserversArray
- 遍历ObserversArray数组,一次取出其中的Observer节点,最终的调用形式如下
[observerNode->observer performSelector: o->selector withObject: notification];
发送通知和接收通知的线程是同一个线程,对于需要在主线程中进行的,可以在接收通知的方法中通过线程通信的方式放到主线程中处理