当前位置: 首页 > news >正文

iOS 通知机制及底层原理

iOS 通知机制及底层原理

文章目录

  • iOS 通知机制及底层原理
    • 前言
    • 关键类
      • NSNotification
      • NSNotificationCenter
        • named表
        • nameless表
        • wildcard表
      • NSNotificationQueue
      • 通知机制
        • 同步通知实现
          • 注册通知
          • 存在`name`
          • 仅仅存在`object`
          • 既没有`name`也没有`object`
          • 发送通知
          • 移除通知
        • 异步通知的实现
          • 入队
          • 发送通知
      • 主线程响应通知
      • 页面销毁的时候不移除通知会崩溃吗

前言

笔者今天简单学习一下有关于我们这里的一个通知的底层原理的内容

关键类

主要分成了NSNotification, NSNotificationCenter 两个部分

NSNotification

@interface NSNotification : NSObject <NSCopying, NSCoding>
...
/* Querying a Notification Object */- (NSString*) name; // 通知的name
- (id) object; // 携带的对象
- (NSDictionary*) userInfo; // 配置信息@end

这里我们看一下通知的一个使用吧:

// 接受通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:nil];
// 发送通知
[NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];

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;

通知中心定义了两个结构体来存储通知信息和观察者信息:

// 根容器,NSNotificationCenter持有
typedef struct NCTbl {Observation		*wildcard;	/* 链表结构,保存既没有name也没有object的通知 */GSIMapTable		nameless;	/* 存储没有name但是有object的通知	*/GSIMapTable	 named;		/* 存储带有name的通知,不管有没有object	*/...
} NCTable;// Observation 存储观察者和响应结构体,基本的存储单元
typedef	struct	Obs {id		observer;	/* 观察者,接收通知的对象	*/SEL		selector;	/* 响应方法		*/struct Obs	*next;		/* Next item in linked list.	*/...
} Observation;
named表

在named表中,NotifcationName作为表的key,table作为表的value.因为我们在注册观察者的时候是可以传入一个参数object用于值监听指定该对象发出的通知,并且一个通知可以添加多个观察者,所以还需要一张表来保存objectobserver的一个关系,这个表的key,value分贝是以object为key,Observer作为value,用来链表这个数据结构保存多个观察者的情况.

这里name不为空的场景中使用

在这里插入图片描述

在实际开发中object参数经常传nil,这个时候系统就会根据nil生成一个key,相当于这个key对应的value保存的就是的当前通知传入了NotificationName,

nameless表

namelss表没有NotificaionName,即没有了最外层一层键值对的约束,其中就只有objectObservation所对应的键值对结构.

这个表的使用场景是name为空,object不为空的场景的时候使用

在这里插入图片描述

wildcard表

这个表既没有NotifciaotnName也没有object了,所以他就会在nameless基础上少了一层键值对,那么他就只剩下一个链表.

这个在nameobject都为空的时候就采用这种方式构建了

在这里插入图片描述

NSNotificationQueue

通知队列,用于异步发送消息,这个异步并不是开启线程,而是把通知存到双向链表实现的队列里面.等待某个时机触发时调用NSNotificationCenter的发送接口进行通知的发送,所以可以理解为NSNotificationQueue最终还是调用NSNotificationCenter进行消息的分发

另外NSNotificationQueue是依赖runloop的,所以如果线程的runloop未开启则无效,NSNotififcationQueue主要做了两件事情:

  • 添加通知到队列
  • 删除通知
// 把通知添加到队列中,NSPostingStyle是个枚举,下面会介绍
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
// 删除通知,把满足合并条件的通知从队列中删除
- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask;

把通知添加到队列等待发送,同时提供了一些附加条件提供开发者选择,如果

// 表示通知的发送时机
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相同
};

通知机制

这里通知机制是通过上面的通知队列实现的,这里主要分成两种通知传递的方式

同步通知实现
注册通知

这里使用addObserver:selector:name:object

- (void) addObserver: (id)observerselector: (SEL)selectorname: (NSString*)nameobject: (id)object
{Observation        *list;Observation        *o;GSIMapTable        m;GSIMapNode        n;// observer为空时的报错if (observer == nil)[NSException raise: NSInvalidArgumentExceptionformat: @"Nil observer passed to addObserver ..."];
// selector为空时的报错if (selector == 0)[NSException raise: NSInvalidArgumentExceptionformat: @"Null selector passed to addObserver ..."];
// observer不能响应selector时的报错if ([observer respondsToSelector: selector] == NO){[NSException raise: NSInvalidArgumentExceptionformat: @"[%@-%@] Observer '%@' does not respond to selector '%@'",NSStringFromClass([self class]), NSStringFromSelector(_cmd),observer, NSStringFromSelector(selector)];}
// 给表上锁lockNCTable(TABLE); //这里的Table就是上面的持有三种数据结构的Table// 创建一个observation对象,持有观察者和SEL,下面进行的所有逻辑就是为了存储它o = obsNew(TABLE, selector, observer);/*======= case1: 如果name存在 =======*/if (name) {//-------- NAMED是个宏,表示名为named字典。以name为key,从named表中获取对应的mapTablen = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);if (n == 0) { // 不存在,则创建 m = mapNew(TABLE); // 先取缓存,如果缓存没有则新建一个mapGSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);...}else { // 存在则把值取出来 赋值给mm = (GSIMapTable)n->value.ptr;}//-------- 以object为key,从字典m中取出对应的value,其实value被MapNode的结构包装了一层,这里不追究细节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;}}
/*======= case2:如果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 { // 存在 则把值接到链表的节点上...}}
/*======= case3:name 和 object 都为空 则存储到wildcard链表中 =======*/else {o->next = WILDCARD;WILDCARD = o;}// 解锁unlockNCTable(TABLE);
}

NCTable结构体中的核心变量就是上面讲到的三个结构体namednamelesswildcard.根据不同的场景来决定他执行的结构体的内容:

存在name

找到name表,这个表存储了name的通知

name作为key,找到了value,这个value在前面讲过仍然是map

前面讲过将object作为key,Observation对象是value,这个Observartion是一个链表的结构,主要存储了对应的obseerver 和 SEL

然后把最开始创建的Observation对象o存储进去

仅仅存在object
  1. object为key,从nameless表中取出value,这个value是一个Observation类型的链表
  2. 然后存储我们的Observer
既没有name也没有object

直接存储到wildcard这个链表结构中

发送通知

使用方法postNotification

// 发送通知
- (void) postNotification: (NSNotification*)notification {if (notification == nil) {[NSException raise: NSInvalidArgumentExceptionformat: @"Tried to post a nil notification."];}[self _postAndRelease: RETAIN(notification)];
}- (void) postNotificationName: (NSString*)nameobject: (id)object {[self postNotificationName: name object: object userInfo: nil];
}- (void) postNotificationName: (NSString*)nameobject: (id)objectuserInfo: (NSDictionary*)info
{
// 构造一个GSNotification对象, GSNotification继承了NSNotificationGSNotification	*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这个方法中,这里我们就简单介绍一下这个方法:

//发送通知的核心函数,主要做了三件事:查找通知、发送、释放资源
- (void) _postAndRelease: (NSNotification*)notification {//step1: 从named、nameless、wildcard表中查找对应的通知...//step2:执行发送,即调用performSelector执行响应方法,从这里可以看出是同步的[o->observer performSelector: o->selectorwithObject: notification];//step3: 释放资源RELEASE(notification);
}

查找通知:

1.首先会创建一个数组 observerArray 用来保存需要通知的 observer。
2.遍历 wildcard 链表,将 observer 添加到 observerArray 数组中。 (因为nil代表接受所有通知)
3.若存在 object,在 nameless table 中找到以 object 为 key 的链表,然后遍历找到的链表,将 observer 添加到 observerArray 数组中。
4.若存在 NotificationName,在 named table 中以 NotificationName 为 key 找到对应的 table,然后再在找到的 table 中以 object 为 key 找到对应的链表,遍历链表,将 observer 添加到 observerArray 数组中。如果 object 不 为nil,则以 nil 为 key 找到对应的链表,遍历链表,将 observer 添加到 observerArray 数组中。
5.至此所有关于当前通知的 observer(wildcard + nameless + named)都已经加入到了数组 observerArray 中。
异步通知实现

发送通知

取出每一个observer节点,然后通过performSelector:逐一调用sel,这个采用同步操作

释放资源:

释放notification对象

从三个存储容器中:namednamelesswildcard去查找对应的Observation对象,然后通过performSelector:逐一调用响应方法,这就完成了发送流程

移除通知

调用removeOberver方法移除通知

- (void) removeObserver: (id)observer {if (observer == nil)return;[self removeObserver: observer name: nil object: nil];
}- (void) removeObserver: (id)observername: (NSString*)nameobject: (id)object {// 当其要移除的信息都为空时,直接返回if (name == nil && object == nil && observer == nil)return;lockNCTable(TABLE);// name和object都为nil,就在wildcard链表里删除对应observer的注册信息if (name == nil && object == nil) {WILDCARD = listPurge(WILDCARD, observer);}// name为空时if (name == nil) {GSIMapEnumerator_t        e0;GSIMapNode                n0;// 首先尝试删除为此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为空,直接清除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的nameif (m->nodeCount == 0) {mapFree(TABLE, m);GSIMapRemoveKey(NAMED, (GSIMapKey)((id)name));}}unlockNCTable(TABLE);
}
  1. 先在我们的wildcard部分移除我们持有observer的所有节点(name == nil 和 object == nil)
  2. 移除指定对象/所有对象的匿名观察者 (name == nil),处理named表和nameless表,如果object == nil的话,那么我们就会情况这个namednameLess,如果不为nil就情况对应的一个链表
  3. 移除指定名称的观察者 (name != nil)处理,如果object == nil那么就情况通过这个name找到的table,如果不为nil就清空object对应的一个链表
异步通知的实现

异步通知机制通过NSNotificationQueue的异步发送,从线程的角度看并不是真正的异步发送,或可称为延时发送,它是利用了runloop的时机来触发的.

入队
/*
* 把要发送的通知添加到队列,等待发送
* NSPostingStyle 和 coalesceMask在上面的类结构中有介绍
* modes这个就和runloop有关了,指的是runloop的mode
*/ 
- (void) enqueueNotification: (NSNotification*)notificationpostingStyle: (NSPostingStyle)postingStylecoalesceMask: (NSUInteger)coalesceMaskforModes: (NSArray*)modes
{......// 判断是否需要合并通知if (coalesceMask != NSNotificationNoCoalescing) {[self dequeueNotificationsMatching: notificationcoalesceMask: coalesceMask];}switch (postingStyle) {case NSPostNow: {...// 如果是立马发送,则调用NSNotificationCenter进行发送[_center postNotification: notification];break;}case NSPostASAP:// 添加到_asapQueue队列,等待发送add_to_queue(_asapQueue, notification, modes, _zone);break;case NSPostWhenIdle:// 添加到_idleQueue队列,等待发送add_to_queue(_idleQueue, notification, modes, _zone);break;}
}
  1. 根据参数来判断是否合并通知
  2. 接着根据postingStyle参数,判断通知发送的时机,如果不是立即发送就是把通知加入队列
发送通知
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触发某个时机,调用GSPrivateNotifyASAPGSPrivateNotifyIdle()方法,这两个反复该最终都调用了notify方法,所做的事情就是调用NSNotificationCenterpostNotification:进行发送通知.

前者是Runloop事件处理周期,后者函数是在Runloop空闲状态

主线程响应通知

异步线程发送通知的话,也是在异步线程接受通知,如果进行UI的处理的话,就会出现问题

  1. 使用addObserverForName: object: queue: usingBlock方法注册通知,指定在主线程上面刷新block
  2. 在主线程注册一个machPort,它是用来做线程通信的,挡在异步线程接收到通知之后,给machPort发送消息,这样就是在主线程响应了

页面销毁的时候不移除通知会崩溃吗

在iOS9之后,通知中心持有观察者的方式从unsafe_retain变成了weak,就算不对观察者手动移除,也会自动释放,不会出现悬垂指针的问题.

但是如果通过addObserverForName:object: queue:usingBlock:方法注册的观察者就需要主动释放.因为通知中心持有了强引用.

@property (nonatomic, strong) id notificationToken;self.notificationToken = [[NSNotificationCenter defaultCenter]addObserverForName:@"MyNotification"object:nilqueue:[NSOperationQueue mainQueue]usingBlock:^(NSNotification *note) {NSLog(@"通知到了");}]; // 这里返回了一个内部的观察者变量,保存它.来帮助我们移除后面的内容//移除操作
[[NSNotificationCenter defaultCenter] removeObserver:self.notificationToken];
http://www.dtcms.com/a/287258.html

相关文章:

  • Java 大视界 -- Java 大数据机器学习模型在自然语言处理中的对话系统多轮交互优化与用户体验提升(351)
  • 【设计模式C#】状态模式(用于解决解耦多种状态之间的交互)
  • 微服务学习(六)之分布式事务
  • 微服务的编程测评系统-网关-身份认证-redis-jwt
  • Spring Boot 与微服务详细总结
  • Error:HTTP Status 405 - HTTP method POST is not supported by this URL
  • Javascript进程和线程通信
  • Uniapp之键盘弹窗
  • day43 CNN及Grad-CAM实战——以Intel Image Classification为例
  • JAVA中的Collections 类
  • [论文阅读] 人工智能 + 软件工程 | 强化学习在软件工程中的全景扫描:从应用到未来
  • ABP VNext + Temporal:分布式工作流与 Saga
  • 当OT遇见IT:Apache IoTDB如何用“时序空间一体化“破解工业物联网数据孤岛困局
  • 时序数据库选型实战:Apache IoTDB技术深度解析
  • Bicep入门篇
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘pillow’问题
  • C/C++---文件读取
  • kotlin部分常用特性总结
  • Node.js net.Socket.destroy()深入解析
  • 海思3516cv610 NPU学习
  • 【C语言进阶】题目练习(3)
  • kafka--基础知识点--6.1--LEO、HW、LW
  • Validation - Spring Boot项目中参数检验的利器
  • web.m3u8流媒体视频处理
  • Flutter基础(前端教程①③-单例)
  • 定时器与间歇函数
  • Web3.0与元宇宙:区块链驱动的数字新生态解析
  • 【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - snowNLP库实现中文情感分析
  • 如何增强LLM(大语言模型)的“置信度”和“自信心” :LLM的“自信”不是“什么都能答”,而是“该答的答得准,不该答的敢说不”。
  • 【unity游戏开发入门到精通——3D篇】3D光源之——unity使用Lens Flare (SRP) 组件实现太阳耀斑镜头光晕效果