一、概述
-
通知经典的使用场景是多对多的场景。
-
Notification相关接口中的参数object表示的是观察者只会接受来至object对象发出的所注册的通知,而不会接受其他对象发送的所注册的通知。
-
通知默认是同步执行机制( 这主要是因为底层使用performSelector的方式进行广播通知。)
即postNotification:
总是会卡住当前线程,待observer执行结束之后才会继续往下执行。
因此,异步线程发送通知则响应函数也是在异步线程,如果需要更新ui则需要额外处理。 -
iOS8之前add和remove必须配套,而iOS9之后不需要。
原因是iOS8之前NSNotificationCenter持有的是观察者的unsafe_unretained
指针。而iOS9之后持有的是weak
指针。因此,即使dealloc的时候未removeOberser
,再进行post操作,则会向nil发送消息,不会crash。 -
源码在GNUStep
二、异步通知
- 利用接口
addObserverForName:object:queue:usingBlock:
来实现异步通知。
通知队列NSNotificationQueue
-
异步发送消息必须借助于
NSNotificationQueue
; -
所谓异步,指的是非实时发送而是在合适的时机发送,并没有开启异步线程;而是把通知存到双向链表实现的队列里面,等待某个时机触发时调用
NSNotificationCenter
的发送接口进行发送通知(即最终还是调用NSNotificationCenter进行消息的分发)。 -
另外
NSNotificationQueue
是依赖runloop的,所以如果线程的runloop未开启则无效。 -
核心API只有两个,如下
// 把通知添加到队列中,NSPostingStyle是个枚举,下面会介绍
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
// 删除通知,把满足合并条件的通知从队列中删除
- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask;
异步通知需要借助使用通知队列(NSNotificationQueue)
- 可以设置,异步通知发送的时机(即在runloop那种状态下触发通知)。
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, // 当runloop处于空闲状态时post
NSPostASAP = 2, // 当当前runloop完成之后立即post
NSPostNow = 3 // 立即post,同步(为什么需要这种type,且看三.3)
};
NSNotification *noti = [NSNotification notificationWithName:@"name" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP];
- 可以设置合成策略(即多个Notification的时候,是否合并)
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
NSNotificationNoCoalescing = 0, // 不合成
NSNotificationCoalescingOnName = 1, // 根据NSNotification的name字段进行合成
NSNotificationCoalescingOnSender = 2 // 根据NSNotification的object字段进行合成
};
NSNotification *noti = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP coalesceMask:NSNotificationNoCoalescing forModes:nil];
- 每个线程有一个默认和
default notification center
相关联的的通知队列? —— 待研究
三、实现原理概述
每个NSNotificationCenter
都有一个默认的_table
,其对observer
进行引用(iOS9以前unsafe_unretained
,iOS9以后weak
),post
的时候,查找通知的观察者对象。注意:在table
中查找observer object
的时候,首先根据object,接下来根据的是name,可见name的优先级比较高。
1、添加监听:主要关注其内部的存储结构
简化后的数据结构如下:
typedef struct NCTbl {
Observation *wildcard; /* 保存既没有没有传入通知名字也没有传入object的通知*/
MapTable nameless; /*保存没有传入通知名字的通知 */
MapTable named; /*保存传入了通知名字的通知,不管有没有object */
} NCTable;
typedef struct Obs {
id observer; /* 保存接受消息的对象*/
SEL selector; /* 保存注册通知时传入的SEL*/
struct Obs *next; /* 保存注册了同一个通知的下一个观察者*/
struct NCTbl *link; /* 保存改Observation的Table*/
} Observation;
其中MapTable
是 key-value
的哈希表,value存放的是存放观察者的struct Obs
对象的单链表;其中而观察者Obs
结构体,内部有next
指向下一个观察者Obs
。
注意:对observer进行引用的方式,在iOS9以前是unsafe_unretained
,iOS9以后weak。
根据name和object是否有值,分一下三种情况进行存储。
-
1、存在name(无论object是否存在),保存在NamedTable(有名字表)
类似字典的表,以name为key,value为另外一张表为UnnamedTable(object为key,value为observer对象)。
image.png
-
2、不存在name,但是存在object的通知,保存在UnnamedTable(无名字表)
类似字典的表,其中以object为key,value为链表,其用来保存所有的观察者Observer对象。
image.png
-
3、既没有name,也没有object,保存在wildcard链表结构中。
wildcard是一个Observation对象链表,内部有个next指针指向下一个;
2、post内部原理
1、通过name & object 查找到所有的obs对象(保存了observer和sel),放到数组中.
2、通过 performSelector:
逐一调用sel,这是个同步操作.
3、释放notification对象.
回调 观察者方法 的方式
[observerNode->observer performSelector: o->selector withObject: notification];
利用performSelector方式,这也就能说明发送通知的线程和接收通知的线程是同一个线程。
参考
NSNotification,看完你就都懂了
深入理解iOS NSNotification
轻松过面:一文全解iOS通知机制
网友评论