这是
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 许可协议进行许可,转载请注明出处!