文章目录
概要
- 通知传值可以跨越多个界面进行传值,支持多个接受者,多个对象可以同时接收一个通知并进处理,实现一对多通信。
实现流程如下:
发送通知:
objc
[[NSNotificationCenter defaultCenter] postNotificationName:@"note"
object:nil userInfo:@{@"name": self.label.text}];
参数一:NSString类型,用于标识通知的唯一性,发送和接收通知的时候这个字段要相同
参数二:发送通知的对象,可以为nil,主要用于区分通知的来源,如果只关心某个特定对象发出的通知,可·· 以指定object,反之传nil就行,
参数三:通知携带的附加信息,字典类型。用于传递额外数据
接收通知:
objc
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificate:) name:@"note" object:nil];
参数一:监听者对象,收到通知之后会回调selector方法。
参数二:SEL类型,回调指定的方法。
参数三:通知的名字。
参数四:直接收由该对象发送的通知,如果传为nil,表示接收所有对象发送的该名字的通知,如果指定了object则只接收这个对象发送的通知。
移除监听者:
objc
[[NSNotificationCenter defaultCenter] removeObserver:self];
通知机制的关键类
NSNotification
首先是一个NSNotification,这是一个用于描述通知的类,一个通知对象包含下面几个属性:
objc
@interface NSNotification : NSObject <NSCopying, NSCoding>
...
/* Querying a Notification Object */
- (NSString*) name; // 通知的名字
- (id) object; // 携带的对象
- (NSDictionary*) userInfo; // 额外信息
@end
其次是一个NSNotificationCenter,通知的核心就是一个与线程关联的对象即通知中心,通知中心发送通知给观察者是同步的,也可以使用NSNotificationQueue队列实现异步发送通知。(延迟到当前线程 RunLoop 的某个阶段发送)这个队列还可以实现一个合并通知的效果,如果短时间内连续入队多个同名通知,最终可能只发送一次,适用于频繁变化的场景。
objc
static NSNotificationCenter *default_center = nil;
+ (NSNotificationCenter*) defaultCenter
{
return default_center;
}
这里主要有三种放松时机:
objc
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostNow];//表示立即发送
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];//尽快发送,会等待当前调用栈结束,runloop有机会时再发送
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle];//等待runloop空闲时再发送,适合一些不紧急的通知,如UI更新、状态刷新之类的
NSNotification依赖于runloop,所以如果当前线程没有正常运行runloop的时候,通知可能不会按照预期发送。(使用GCD的时候需要注意,子线程中也需要注意)
我们也可以指定在某种RunLoop Mode下发送,如果传nil表示默认模式。
objc
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:@[NSDefaultRunLoopMode]];
NSNotificationCenter类主要负责三件事:
- 添加通知
- 发送通知
- 移除通知
objc
// 添加通知
- (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;
通知中心定义了两个结构体存储通知信息和观察者信息:
objc
// 管理所有的观察者节点
typedef struct NCTbl {
Observation *wildcard;;//存储所有的通知的观察者的一个链表
GSIMapTable nameless;//存储没有执行通知名字,但是指定了object的观察者表
GSIMapTable named;//存储指定了通知名的观察者表
//...
} NCTable;
// 存储观察者和响应结构体,是基本的存储单元,保存一次完整的注册关系
typedef struct Obs {
id observer;
id receiver;
SEL selector;
BOOL owner;
int32_t posting;
struct Obs *next;
struct NCTbl *link;//反向指向通知中心的内部表
} Observation;
我们先简单看一个查找流程:
objc
post notification
↓
取出 notification.name / notification.object
↓
查 wildcard 链表
↓
查 nameless[object]
↓
查 named[name][object]
↓
查 named[name][nil]
↓
得到 Observation 数组
↓
依次调用 observer 的 selector
name表
named表是一个二级Map,大概结构如下:
objc
named = {
notificationName : {
object : Observation链表
}
}
第一层的key是通知的名字,value是objectMap
第二层的key是发送者object,value是一个Observation链表
内层使用链表结构实现保存多个观察者的情况。

在实际开发中,object参数经常传nil,系统会自动为根据这个nil生成一个key。
nameless表
由于没有通知名,因此就没了第一层的约束,只有object和observation的结构了。

wildcard表
这个表没有通知名也没有object,所以直接退化为了一个链表,存储了可以接收所有通知的类的信息。

NSNotificationQueue
objc
NSNotification *notification = [NSNotification notificationWithName:@"DataDidChange" object:self userInfo:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];
通知队列,主要用于异步发送消息,这里需要注意的是,这个异步不是开启了一个新线程,而是将通知存储到双向链表实现的队列中,等待runloop到合适的时机进行调用,最终还是通过调用NSNotificationCenter进行消息的分法
NSNotification主要做的事由两件:
objc
// 把通知添加到队列中
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
// 删除通知,将满足合并条件的通知从队列中删除
- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask;
几个枚举参数结构如下:
objc
// 表示通知的发送时机
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相同
};
同步通知机制代码
注册通知
这段代码的核心任务是把observer+selector这一组信息,按照name / object的匹配条件存储进通知中心内部的表结构中。
objc
- (void)addObserver:(id)observer selector:(SEL)selector name:(NSString *)name object:(id)object
{
Observation *list;//已有观察者链表的头节点
Observation *o;
GSIMapTable m;
GSIMapNode n;
if (observer == nil)//观察者不能为空
[NSException raise:NSInvalidArgumentException
format:@"Nil observer passed to addObserver ..."];
if (selector == 0)//没有指定回调方法
[NSException raise:NSInvalidArgumentException
format:@"Null selector passed to addObserver ..."];
if ([observer respondsToSelector:selector] == NO)//观察者没法响应selector
{
[NSException raise:NSInvalidArgumentException
format:@"[%@-%@] Observer '%@' does not respond to selector '%@'",
NSStringFromClass([self class]), NSStringFromSelector(_cmd),
observer, NSStringFromSelector(selector)];
}
lockNCTable(TABLE);//加锁,避免链表损坏等
o = obsNew(TABLE, selector, observer);//创建通知中心真正的观察者节点
if (name)
{
n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
if (n == 0)
{
m = mapNew(TABLE);
GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void *)m);
...
}
else
{
m = (GSIMapTable)n->value.ptr;
}
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;
}
}
else if (object)
{
n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
if (n == 0)
{
o->next = ENDOBS;
GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
}
else
{
...
}
}
else
{
o->next = WILDCARD;
WILDCARD = o;
}
unlockNCTable(TABLE);
}
发送通知
将传入的通知参数封装成一个NSNotification/GSNotification对象,调用内部方法去真正发送通知。
objc
// 发送通知
- (void) postNotification: (NSNotification*)notification {
if (notification == nil) {
[NSException raise: NSInvalidArgumentException
format: @"Tried to post a nil notification."];
}
[self _postAndRelease: RETAIN(notification)];
}
- (void) postNotificationName: (NSString*)name object: (id)object {
[self postNotificationName: name object: object userInfo: nil];
}
- (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];
}
通过上面代码,可以看到最终都是进入了一个_postAndRelease:核心作用可以概括为:++根据通知的name和object找到所有符合条件的观察者,然后逐个调用观察者注册时指定的selector,最后释放notification++。
objc
- (void) _postAndRelease: (NSNotification*)notification {
Observation *o;//一个观察者记录
unsigned count;//临时数组中观察者数量
NSString *name = [notification name];
id object;//保存通知携带的object
GSIMapNode n;
GSIMapTable m;
GSIArrayItem i[64];
GSIArray_t b;
GSIArray a = &b;
if (name == nil) {//发送通知时,通知名不能为nil(注册观察者时可以不指定通知的名称)
RELEASE(notification);
[NSException raise: NSInvalidArgumentException
format: @"Tried to post a notification with no name."];
}
object = [notification object];//取出通知的object
GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);//创建一个临时数组啊,用于保存本次通知要发送给哪些观察者,如果观察者数量不超过64可以直接使用栈上的空间,避免堆分配,提高性能
lockNCTable(TABLE);
//完全通配观察者,即object、name都为nil。purge方法会清理掉无效的节点
for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next) {
GSIArrayAddItem(a, (GSIArrayItem)o);
}
//处理没有name的情况
if (object) {
n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);//得到object对应的observation链表
if (n != 0) {//找到了
o = purgeCollectedFromMapNode(NAMELESS, n);//清理其中已经失效的观察者
while (o != ENDOBS) {//遍历加入观察者数组
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
}
}
}
if (name) {
n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
if (n) {
m = (GSIMapTable)n->value.ptr;//取出这个节点保存的值
} else {
m = 0;
}
if (m != 0) {//确实有观察者链表,先获取
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
if (n != 0) {
o = purgeCollectedFromMapNode(m, n);
while (o != ENDOBS) {
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
}
}
if (object != nil) {
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);
if (n != 0) {
o = purgeCollectedFromMapNode(m, n);
while (o != ENDOBS) {
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
}
}
}
}
}
unlockNCTable(TABLE);
//发送通知
count = GSIArrayCount(a);
while (count-- > 0) {
o = GSIArrayItemAtIndex(a, count).ext;
if (o->next != 0) {
NS_DURING {
[o->observer performSelector: o->selector
withObject: notification];
}
NS_HANDLER {
BOOL logged;
NS_DURING
NSLog(@"Problem posting %@: %@", notification, localException);
logged = YES;
NS_HANDLER
logged = NO;
NS_ENDHANDLER
if (NO == logged) {
NSLog(@"Problem posting notification: %@", localException);
}
}
NS_ENDHANDLER
}
}
lockNCTable(TABLE);
GSIArrayEmpty(a);
unlockNCTable(TABLE);
RELEASE(notification);
}
我们使用自然语言分析一下这段代码:
整体的流程可以分为:查找通知、发送、释放资源三个流程
- 查找通知:通配观察者 -> 无name但是object匹配 -> name匹配object随便
- 发送通知:从观察者数组中取出observer节点,通过performSelector:逐一调用sel
- 释放资源:释放notification对象
删除通知
通过调用removeObserver方法实现移除通知:
objc
- (void) removeObserver: (id)observer {
if (observer == nil)
return;
[self removeObserver: observer name: nil object: nil];
}
- (void) removeObserver: (id)observer name: (NSString*)name object: (id)object {
// 当要移除的信息都为空时,直接返回
if (name == nil && object == nil && observer == nil)
return;
lockNCTable(TABLE);
//在wildcard链表里删除对应observer的注册信息,然后返回新的链表头
if (name == nil && object == nil) {
WILDCARD = listPurge(WILDCARD, observer);
}
//不限制通知名,这里包含两种情况,可能覆盖所有的name,需要根据ojbect进行区分
if (name == nil) {
GSIMapEnumerator_t e0;//枚举器
GSIMapNode n0;//当前遍历到的map节点
// 首先尝试删除为此object对应的所有命名项目
// 在named表中
e0 = GSIMapEnumeratorForMap(NAMED);
n0 = GSIMapEnumeratorNextNode(&e0);
while (n0 != 0) {
GSIMapTable m = (GSIMapTable)n0->value.ptr;
NSString *thisName = (NSString*)n0->key.obj;
n0 = GSIMapEnumeratorNextNode(&e0);
if (object == nil) { // 如果object为空即清理某个observer在所有object下的注册,直接清除named表
// 清空named表
GSIMapEnumerator_t e1 = GSIMapEnumeratorForMap(m);
GSIMapNode n1 = GSIMapEnumeratorNextNode(&e1);
while (n1 != 0) {
GSIMapNode next = GSIMapEnumeratorNextNode(&e1);
purgeMapNode(m, n1, observer);
n1 = next;
}
} else {
// 以object为key找到对应链表,清空该链表
GSIMapNode n1;
n1 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
if (n1 != 0) {
purgeMapNode(m, n1, observer);
}
}
if (m->nodeCount == 0) {
mapFree(TABLE, m);
GSIMapRemoveKey(NAMED, (GSIMapKey)(id)thisName);
}
}
// 开始操作nameless表
if (object == nil) { // object为空时
// 清空nameless表
e0 = GSIMapEnumeratorForMap(NAMELESS);
n0 = GSIMapEnumeratorNextNode(&e0);
while (n0 != 0) {
GSIMapNode next = GSIMapEnumeratorNextNode(&e0);
purgeMapNode(NAMELESS, n0, observer);
n0 = next;
}
} else { // object不为空
// 找到对应的observer链表,清空该链表
n0 = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
if (n0 != 0) {
purgeMapNode(NAMELESS, n0, observer);
}
}
} else { // name不为空
GSIMapTable m;
GSIMapEnumerator_t e0;
GSIMapNode n0;
n0 = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
// 如果没有和这个name相同的key,直接返回
if (n0 == 0) {
unlockNCTable(TABLE);
return; /* Nothing to do. */
}
m = (GSIMapTable)n0->value.ptr; // 找到name作为key对应的数据信息
if (object == nil) {
// 如果object为nil,就清空刚才找到的name对应的数据信息
e0 = GSIMapEnumeratorForMap(m);
n0 = GSIMapEnumeratorNextNode(&e0);
while (n0 != 0) {
GSIMapNode next = GSIMapEnumeratorNextNode(&e0);
purgeMapNode(m, n0, observer);
n0 = next;
}
} else {
// 如果object不为空,清空object对应的链表
n0 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
if (n0 != 0) {
purgeMapNode(m, n0, observer);
}
}
// 因为其中的数据清除完了,所以还需要清除named表中的作为key的name
if (m->nodeCount == 0) {
mapFree(TABLE, m);
GSIMapRemoveKey(NAMED, (GSIMapKey)((id)name));
}
}
unlockNCTable(TABLE);
}
我们同样使用自然语言概述如下:
- 如果name和object都为nil:只需要在wildcard链表中移除observer
- 如果name为nil:object有两种情况
- named表:遍历所有name,找到每个name下的object,如果object为空,就直接清空named表,反之只清空指定object的。如果某个name下所有的object都被清空了,就直接讲这个name从named表中移除
- nameless表:如果object为nil,就清空nameless表中所有的observer。如果不为空,只清理object对应的observer
- 如果name不为空:
- 首先在named表中找到name对应的map,如果object为nil,就清空改name下所有的observer。如果object不为nil,就只清空name下object对应的observer,如果name下所有的observer都被清空,需要讲name从named表中移除。
异步通知实现机制
如前面所介绍的,异步通知机制是使用NSNotificationQueue实现的,但是并没有真正创建子线程,只是利用率NSRunLoop实现的一个延迟发送。
入队
objc
// 定义通知发送时机的枚举
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相同,则认为通知重复,合并处理
};
- (void)enqueueNotification:(NSNotification *)notification
postingStyle:(NSPostingStyle)postingStyle
coalesceMask:(NSUInteger)coalesceMask
forModes:(NSArray *)modes
{
// 如果设置了合并策略,先移除已有队列中与当前通知重复的通知
if (coalesceMask != NSNotificationNoCoalescing) {
// dequeueNotificationsMatching:coalesceMask: 方法会根据策略
// 删除name或object重复的通知,确保队列中不会重复发送
[self dequeueNotificationsMatching:notification
coalesceMask:coalesceMask];
}
// 根据发送策略选择队列
switch (postingStyle) {
case NSPostNow: {
// 立即发送通知,不加入队列
// 如果当前正在处理通知队列,也可能会合并其他待发送通知
[_center postNotification:notification];
break;
}
case NSPostASAP: {
// 尽快发送,加入_asapQueue队列
// _asapQueue会在事件处理完成后尽快被处理
// modes指定通知在哪些runloop mode下可以被触发
add_to_queue(_asapQueue, notification, modes, _zone);
break;
}
case NSPostWhenIdle: {
// 空闲时发送,加入_idleQueue队列
// _idleQueue通常在runloop空闲时才会处理
add_to_queue(_idleQueue, notification, modes, _zone);
break;
}
default:
// 未知postingStyle,通常不会走到这里
break;
}
}
发送通知
objc
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);
}
......
}
// 发送_asapQueue中的通知
void GSPrivateNotifyASAP(NSString *mode) {
notify(item->queue->_center,
item->queue->_asapQueue,
mode,
item->queue->_zone);
}
// 发送_idleQueue中的通知
void GSPrivateNotifyIdle(NSString *mode) {
notify(item->queue->_center,
item->queue->_idleQueue,
mode,
item->queue->_zone);
}
runloop会在合适的时机触发,调用GSPrivateNotifyASAP()和GSPrivateNotifyIdle()方法,两个方法最终都调用notify()方法
主线程响应通知
如果异步线程发送通知则相应函数也是在异步线程,如果此时执行UI刷新的话就会出现问题。对此我们有两种情况解决这个问题:
-
使用
addObserverForName: object: queue: usingBlock方法注册通知,指定在mainqueue上响应block -
利用前面学习的runloop线程保活,在主线程注册machPort,通过线程通信,在异步线程收到消息的时候给machPort发送消息
在iOS9开始,页面销毁时不移除通知并不会崩溃,这是因为通知中心持有的观测者的引用由unsafe_unretained变为weak,持有观测者的引用会在观测者被回收后自动置空。但是通过
addObserverForName:object: queue:usingBlock:方法注册的观察者需要手动释放,因为通知中心持有的是它们的强引用。