这是
iOS提供的一种 “同步的” 消息通知机制,观察者只要向消息中心注册, 即可接受其他对象发送来的消息,消息发送者和消息接受者两者可以互相一无所知,完全解耦。这种消息通知机制可以应用于任意时间和任何对象,观察者可以有多个,所以消息具有广播的性质.
今天研究下其内部实现跟使用.先来看下官方给我们提供的几个API:
1 | - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject; |
由于NSNotificationCenter 的源码苹果并没有开源,所以还是打算使用GNU源码进行分析.
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject; 我们经常会通过这个方法注册一个通知, 来看下内部是怎么样注册的
NSNotificationCenter在初始化的时候就会初始化一个Table,这个Table会引用注册的Observation,我们来看下Observation 的数据结构
1 | - (id)init { |
1 | typedef struct Obs { |
1 | /* |
注释都比较清晰了,每次调用addObserver方法都会创建一个Observation结构体,其包括了方法、name和object, 每个结构体都会放在一个链表中, NAME或者OBJECT作为key, 如果其next为空,则表示observation未使用或者从链表中移除了或未添加到链表中,链表最后一个next节点会被设成ENDOBS.
但是唯一奇怪的是类型为NCTbl的结构体, 这个NCTbl是这个什么的东西???
NCTbl 的数据结构:
1 | typedef struct NCTbl { |
NC table这个结构是用来记录存储的Observation的内存使用情况,如果Observation被从center移除,内存将会还给chunk table的一个空list, 出于性能的考虑,NC table还提供了缓存机制,避免在频繁添加跟删除Observation时,不停的创建、销毁table
了解这些数据结构之后,我们来看下addObserver这个方法内部干了什么:
1 | - (void) addObserver: (id)observer selector: (SEL)selector name: (NSString*)name object: (id)object { |
去掉异常处理部分之后,大概代码是这样,如果在table中有相同的name的链表, 则取出list并增加一个新的节点;否则将新建一个. 如果有object, 且table中有, 则重新给list赋新值, 其就是一个add的过程.
接下来看下发送通知的方法:
1 | - (void)postNotification:(NSNotification *)notification; |
这三个方法内部都会执行[self _postAndRelease: notification], 也就是说_postAndRelease才是发送通知的核心方法
1 | - (void) _postAndRelease: (NSNotification*)notification { |
这就是发送通知的核心方法了,由于代码太多, 只列出了部分代码, 这个方法里面做了几件事情, 找出所有没有name也没有object的observer; 找出只有object没有name的observer; 找出除了不为nil与notification's OBJECT不匹配有name的observer; 最后遍历所有observer, 并调用performSelector.
1 | - (id) performSelector: (SEL)aSelector withObject: (id)anObject { |
performSelector这个方法的实现并不复杂,通过runtime的方法找出类中对应aSelector的IMP, 并返回函数指针
对应的移除通知函数跟添加通知也是差不多的机制,这里就不再展开了, 就是通过对应的key找到对应的Observation, 并从Table中移除
1 | - (void)removeObserver:(id)observer; |
使用
了解了原理之后,我们来看下它的用法以及使用过程中需要注意的地方:
注册通知有两种方式:
1 | - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject; |
- 第一种是我们常使用的方法,注册观察者,并传入接收通知时需要执行的方法,如果
object传nil的时候,表示可以接收到name相同的所有通知,如果不是nil,将只能接收到name和object都相同的通知 - 第二种方法是基于
Block来添加观察者,这个观察者包括一个queue和一个block,并且会返回这个观察者对象,当接到通知时执行block所在的线程为添加观察者时传入的queue参数,queue也可以为nil,那么block就在通知所在的线程同步执行
发送通知的三种方式:
1 | - (void)postNotification:(NSNotification *)notification; |
- 三种方式都是发送
NSNotification对象给通知中心注册的观察者 - 发送通知通过
name和object来确定来标识观察者,name和object两个参数的规则相同. 即当通知设置name为某个值时,那么只会发送给name相同的观察者,如果name为nil时那么就会对所有的观察者发送通知; 同理object指发送给某个特定对象通知,当设置为nil时表示所有对象都会通知, 那么如果同时设置name和object参数时就必须同时相同的观察者才能接收到通知. 如果两个参数都为nil那么就是所有观察者都会收到通知
在对象被释放前需要移除掉观察者,避免已经被释放的对象还接收到通知导致崩溃。
移除通知有两种方式:
1 | - (void)removeObserver:(id)observer; |
- 第一种我们可以传入需要移除的
observer - 第二种方式通过三个参数来移除指定某个观察者
NSNotificationQueue (通知队列)
通知队列就是将通知放入一个队列里,适当的时机将通知发出去。
通知队列有2个重要的特性:通知合并和异步发送
通知合并
使用NSNotificationQueue的enqueueNotification:postingStyle:coalesceMask:forModes:方法,设置第三个参数coalesceMask的值,来指定不同的合并规则,
1 | typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) { |
设置合并规则后加入到通知队列中,通知队列会按照给定的合并规则,在之前入队的通知中查找,然后移除符合合并规则的通知,这样就达到了只发送一个通知的目的
1 | NSNotification *notification = [NSNotification notificationWithName:@"NotificationName" object:nil]; |
异步发送
使用一下方法方法,将通知加到通知队列中,就可以将一个通知异步的发送到当前的线程,这些方法调用后会立即返回,不用再等待通知的所有监听者都接收并处理完。
1 | - (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle; |
第二个方法中,有一个modes参数,当指定了某种特定runloop mode后,该通知值有在当前runloop为指定mode的下,才会被发出
postingStyle有三种类型:
1 | typedef NS_ENUM(NSUInteger, NSPostingStyle) { |
总结
通过对GNU源码的学习,对通知机制与实现以及用法又有了进一步的理解, NSNotificatonCenter默认是以同步的方式发送通知的,也就是说,当一个对象发送了一个通知,只有当该通知的所有接受者都接受到了NSNotificatonCenter分发的通知消息并且处理完成后,发送通知的对象才能继续执行接下来的方法; 而NSNotificationQueue则可以将一个通知异步的发送到当前的线程,方法调用后会立即返回,不用再等待通知的所有监听者都接收并处理完, 通知队列也能完成对通知的合并.
最后更新: 2023年03月25日 22:39:55
本文链接: http://aeronxie.github.io/post/66c23b87.html
版权声明: 本作品采用 CC BY-NC-SA 4.0 许可协议进行许可,转载请注明出处!