原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 一、使用
- 1、提供的属性和方法
- 2、队列的合并策略和发送时机
- 3、注意点
- 二、注册通知源码解析
- 1、存储容器
- 2、解析注册通知方法
- 3、判断是否是同一个通知的3种情况
- 三、发送通知与删除通知源码解析
- 1、发送通知源码解析
- 2、删除通知源码解析
- 四、异步通知
- 1、NSNotificationQueue的异步发送
- 2、把要发送的通知添加到队列,等待发送
- 3、发送通知
- 4、主线程响应通知
- Demo
- 参考文献
一、使用
1、提供的属性和方法
NSNotification
- (NSString*) name; // 通知的name
- (id) object; // 携带的对象
- (NSDictionary*) userInfo; // 配置信息
NSNotificationCenter
// 添加通知
- (void)addObserver:(id)observer selector:(SEL)aSelector 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;
// 删除通知
- (void)removeObserver:(id)observer;
NSNotificationQueue
// 把通知添加到队列中,NSPostingStyle是个枚举
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
// 删除通知,把满足合并条件的通知从队列中删除
- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask;
用于异步发送消息的通知队列,这个异步并不是开启线程,而是把通知存到双向链表实现的队列里面,等待某个时机触发。触发时调用NSNotificationCenter
的发送接口进行发送通知,这么看NSNotificationQueue
最终还是调用NSNotificationCenter
进行消息的分发,另外NSNotificationQueue
是依赖runloop
的,所以如果线程的runloop
未开启则无效。
2、队列的合并策略和发送时机
把通知添加到队列等待发送,同时提供了一些附加条件供开发者选择,如:什么时候发送通知、如何合并通知等,系统给了如下定义。
表示通知的发送时机
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
NSPostWhenIdle = 1, // runloop空闲时发送通知
NSPostASAP = 2, // 尽快发送,这种情况稍微复杂,这种时机是穿插在每次事件完成期间来做的
NSPostNow = 3 // 立刻发送或者合并通知完成之后发送
};
通知合并的策略,有些时候同名通知只想存在一个,这时候就可以用到它了
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
NSNotificationNoCoalescing = 0, // 默认不合并
NSNotificationCoalescingOnName = 1, // 只要name相同,就认为是相同通知
NSNotificationCoalescingOnSender = 2 // object相同
};
3、注意点
页面销毁时不移除通知会崩溃吗
可以,因为notificationcenter
对观察者的引用是weak
,当观察者释放的时候,观察者的指针值被置为nil
多次添加同一个通知会是什么结果?多次移除通知呢
会调用多次observer
的action
。多次移除没有任何影响。
二、注册通知源码解析
1、存储容器
NCTable是根容器,由NSNotificationCenter持有
NCTable
结构体中核心的三个变量:wildcard
、named
、nameless
,在源码中直接用宏定义表示了:WILDCARD
、NAMELESS
、NAMED
。
typedef struct NCTbl
{
// 链表结构,保存既没有name也没有object的通知
Observation *wildcard;
// 存储没有name但是有object的通知
GSIMapTable nameless;
// 存储带有name的通知,不管有没有object
GSIMapTable named;
...
} NCTable;
Observation是存储观察者和响应方法的结构体
typedef struct Obs
{
id observer;// 观察者,接收通知的对象
SEL selector;// 响应方法
struct Obs *next;// 链表中的下一个Observation
...
} Observation;
2、解析注册通知方法
- 判定是不是同一个通知要从
name
和object
区分,如果他们都相同则认为是同一个通知,后面包括查找逻辑、删除逻辑都以此为基础。 - 存储过程并没有做去重操作,这也解释了为什么同一个通知注册多次则响应多次
a、提供给外界使用的注册通知方法
- observer:观察者,即通知的接收者
- selector:接收到通知时的响应方法
-
name:通知
name
- object:携带对象
- (void) addObserver: (id)observer selector: (SEL)selector name: (NSString*)name object: (id)object
{
// 前置条件判断
...
// 创建一个observation对象,持有观察者和SEL,下面进行的所有逻辑就是为了存储它
o = obsNew(TABLE, selector, observer);
...
}
b、情况一:如果name存在
if (name) {...}
❶ NAMED
是个宏,表示名为named
的字典。如果通知的name
存在,则以name
为key
从named
字典中取出值n
(这个n
其实被MapNode
包装了一层,便于理解这里直接认为没有包装),这个n
还是个字典。
n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
❷ n
不存在,则先取缓存,如果缓存没有则新建一个map
if (n == 0)
{
m = mapNew(TABLE);
GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
...
}
❸ n
存在则把值取出来赋值给m
else
{
m = (GSIMapTable)n->value.ptr;
}
❹ 然后以object
为key
,从字典中取出对应的值,这个值就是Observation
类型的链表,然后把刚开始创建的Observation
对象o
存储进去。
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
if (n == 0)
{// 不存在,则创建
o->next = ENDOBS;
GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
}
else
{
list = (Observation*)n->value.ptr;
o->next = list->next;
list->next = o;
}
c、情况二:如果name为空,但object不为空
else if (object)
{
...
}
❶ 以object
为key
,从nameless
字典中取出对应的value
,value
是个链表结构。
n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
❷ 不存在则新建链表,并存到map
中
if (n == 0)
{
o->next = ENDOBS;
GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
}
❸ 存在则把值接到链表的节点上
else
{
...
}
d、情况三:name 和 object 都为空则存储到wildcard链表中
else
{
o->next = WILDCARD;
WILDCARD = o;
}
3、判断是否是同一个通知的3种情况
情况一:存在name(无论object是否存在)
如果注册通知时传入name
,那么会是一个双层的存储结构。首先找到NCTable
中的named
表,这个表存储了name
的通知。接着以name
作为key
,找到value
,这个value
依然是一个map
。最后,map
的结构是以object
作为key
,Observation
对象为value
,这个Observation
对象的结构上面已经解释,主要存储了observer & SEL
。
情况二:只存在object
以object
为key
,从nameless
字典中取出value
,此value
是个Observation
类型的链表。接着把创建的Observation
类型的对象o
存储到链表中。只存在object
时存储只有一层,那就是object
和Observation
对象之间的映射。
情况三:没有name和object
这种情况直接把Observation
对象存放在了Observation *wildcard
链表结构中。
三、发送通知与删除通知源码解析
1、发送通知源码解析
发送通知的核心逻辑比较简单,基本上就是查找和调用响应方法,从三个存储容器中:named
、nameless
、wildcard
去查找对应的Observation
对象,然后通过performSelector
:逐一调用响应方法,这就完成了发送流程。
a、解析发送通知方法
- (void)postNotificationName: (NSString*)name object: (id)object userInfo: (NSDictionary*)info
{
...
}
❶ 构造一个GSNotification对象, GSNotification继承了NSNotification
GSNotification *notification;
notification = (id)NSAllocateObject(concrete, 0, NSDefaultMallocZone());
notification->_name = [name copyWithZone: [self zone]];
notification->_object = [object retain];
notification->_info = [info retain];
❷ 进行发送操作
[self _postAndRelease: notification];
b、发送通知的核心函数
主要做了三件事:查找通知、发送、释放资源。
- (void)_postAndRelease: (NSNotification*)notification
{
...
}
❶ 通过name & object
从named
、nameless
、wildcard
表中查找对应的通知(保存了observer
和sel
)。
...
❷ 执行发送,即调用performSelector
执行响应方法,从这里可以看出是同步的。
[o->observer performSelector: o->selector
withObject: notification];
❸ 释放notification
对象。
RELEASE(notification);
2、删除通知源码解析
因为查找时做了这个链表的遍历,所以删除时会把重复的通知全都删除掉
- (void)removeObserver: (id)observer
{
if (observer == nil) return;
[self removeObserver: observer name: nil object: nil];
}
查找时仍然以name
和object
为准,再加上observer
做区分。
- (void)removeObserver: (id)observer name: (NSString*)name object: (id)object
{
if (name == nil && object == nil && observer == nil)
return;
...
}
四、异步通知
1、NSNotificationQueue的异步发送
上面介绍的NSNotificationCenter
都是同步发送的,接受消息和发送消息是在一个线程里。这里介绍关于NSNotificationQueue
的异步发送,通过NSNotificationQueue
将通知添加到队列当中,立即将控制权返回给调用者,在合适的时机发送通知,从而不会阻塞当前的调用。从线程的角度看并不是真正的异步发送,或可称为延时发送,它是利用了runloop
的时机来触发的,所以如果在其他子线程使用NSNotificationQueue
,需要开启runloop
。由于最终还是通过NSNotificationCenter
进行发送通知,所以从这个角度讲它还是同步的。所谓异步,指的是非实时发送而是在合适的时机发送,并没有开启异步线程。
2、把要发送的通知添加到队列,等待发送
NSPostingStyle
和 coalesceMask
在上面的类结构中有介绍。modes
这个就和runloop
有关了,指的是runloop
的mode
。
- (void) enqueueNotification: (NSNotification*)notification
postingStyle: (NSPostingStyle)postingStyle
coalesceMask: (NSUInteger)coalesceMask
forModes: (NSArray*)modes
{
...
}
❶ 根据coalesceMask
参数判断是否合并通知
if (coalesceMask != NSNotificationNoCoalescing)
{
[self dequeueNotificationsMatching: notification
coalesceMask: coalesceMask];
}
❷ 接着根据postingStyle
参数,判断通知发送的时机
switch (postingStyle)
{
...
}
❸ runloop
立即回调通知方法,同步发送
case NSPostNow:
{
// 如果是立马发送,则调用NSNotificationCenter进行发送
[_center postNotification: notification];
}
❹ runloop
在执行timer
事件或sources
事件的时候回调通知方法,异步发送
case NSPostASAP:
// 添加到_asapQueue队列,等待发送
add_to_queue(_asapQueue, notification, modes, _zone);
❺ runloop
空闲的时候回调通知方法,异步发送
case NSPostWhenIdle:
// 添加到_idleQueue队列,等待发送
add_to_queue(_idleQueue, notification, modes, _zone);
3、发送通知
runloop
触发某个时机,调用GSPrivateNotifyASAP()
和GSPrivateNotifyIdle()
方法,这两个方法最终都调用了notify()
方法。notify()
所做的事情就是调用NSNotificationCenter
的postNotification:
进行发送通知。
a、发送_asapQueue中的通知
void GSPrivateNotifyASAP(NSString *mode)
{
notify(item->queue->_center,
item->queue->_asapQueue,
mode,
item->queue->_zone);
}
b、发送_idleQueue中的通知
void GSPrivateNotifyIdle(NSString *mode)
{
notify(item->queue->_center,
item->queue->_idleQueue,
mode,
item->queue->_zone);
}
c、循环遍历发送通知
static void notify(NSNotificationCenter *center,
NSNotificationQueueList *list,
NSString *mode, NSZone *zone)
{
for (pos = 0; pos < len; pos++)
{
NSNotification *n = (NSNotification*)ptr[pos];
[center postNotification: n];
RELEASE(n);
}
}
4、主线程响应通知
异步线程发送通知则响应函数也是在异步线程,如果执行UI刷新相关的话就会出现问题,那么如何保证在主线程响应通知呢?可以使用addObserverForName: object: queue: usingBlock
方法注册通知,指定在mainqueue
上响应block
。
Demo
Demo在我的Github上,欢迎下载。
BasicsDemo