NSNotification 通知机制的核心:通知中心 (NSNotificationCenter) 是一个与线程关联的【单例对象】 单例:通知中心是个单例 通知中心发送通知给观察者是同步的,也可以用通知队列 (NSNotificationQueue)异步发送通知 方法: add,post,remove // 添加通知观察者 - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject; - (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block; // 发出通知 - (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; - (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject; 参数: notificationName:为nil 通知中心会通知所有与该通知中object相匹配的监听对象。 若anObject为 nil 通知中心会通知所有与该通知中notificationName相匹配的监听对象 移除观察者 1. 不需要移除 从iOS9开始,即使不移除观察者对象,程序也不会出现异常。 观察者由==unsafe_unretained==引用变为==weak==引用 2. 需要移除 通过`addObserverForName:object: queue:usingBlock:`方法注册的观察者需 要手动释放,因为通知中心持有的是它们的强引用。 NSNotificationQueue 多线程 1.通知的发送和接收都是在 同一个线程: 无论在哪个线程中注册了观察者,通知的发送和接收都是在同一个线程中 2. 通知的接收 所在线程 与 注册通知所在线程无关 接收线程 == 发送线程 在主线程注册了观察者,然后在子线程发送通知,最后接收和处理通知也是在子 线程。一般情况下,发送通知所在的线程就是接收通知所在的线程。 指定接收线程 可以统一所有接收通知后在主线程回调,也可以不封装,由各业务同学自己根据 情况处理 实现原理: NSConcreteNotification `NSNotification`是一个类蔟不能够实例化,当我们调用 `initWithName:object:userInfo:`方法的时候,系统内部会实例化 `NSNotification`的子类`NSConcreteNotification`。在这个子类中重写了 `NSNofication`定义的相关字段和方法。 NSNotificationCenter `NSNotificationCenter`是通知的管理类,实现较复杂。 `NSNotificationCenter`中主要定义了两个table,同时也定义了 `Observation`保存观察者信息 关键对象 NotificationName 通知名称 observer 观察者 object 监听者 通知设计是根据这几个参数对象的有无来决定的 通知存取原理 注册通知就是将对应信息和对象映射到表 或者 链表中,取值也是根据响应key'去 typedef struct NCTbl { GSIMapTable named; /* 有通知名称 的哈希表 */ GSIMapTable nameless; /* 没有通知名称,有object 的表 */ Observation *wildcard; /* 没有name和object 的链表 */ unsigned lockCount; /* Count recursive operations. */ NSRecursiveLock *_lock; /* Lock out other threads. */ Observation *freeList; Observation **chunks; unsigned numChunks; GSIMapTable cache[CACHESIZE]; unsigned short chunkIndex; unsigned short cacheIndex; } NCTable; 从上面结构可以看出,数据映射表设计依赖参数有无 object为空的,内部自动生成对应的key 表named原理示意图 有通知名 named 表结构 key notificationName1 1 notificationName2 2 ... 3 value objTable1 1 objTable2 2 ... 3 named 表中value对应object 表: objTable1 表结构【nameless 表】 key object 1 1 object 2 2 ... 3 value obsever 链表 1 1 obsever 链表 2 2 ... 3 object 表 对应observer 观察者链表: 【wildcard 链表】:head observer -------- next observer -------- next ... null nameless 表: 就是上面 object 表 wildcard 链表: 就是上面的 observer 链表 observer 结构: typedef struct Obs { id observer; /* 接收通知的对象(注册者). */ SEL selector; /* 接收通知调用的方法 */ struct Obs *next; /* 通知名和object相同时,obs链表下一个obsver */ int retained; /* Retain count for structure. */ struct NCTbl *link; /* Pointer back to chunk table */ } Observation; 添加观察者流程 所有注册观察者的操作最后都会调用`addObserver:selector:name:object:` 1. 首先会根据传入的参数实例化一个Observation,Observation对象保存了观 察者对象,接收到通知观察者所执行的方法,以及下一个Observation对象的地 址。 2. 根据是否传入NotificationName选择操作Named Table还是Nameless Table。 3. 若传入了NotificationName,则会以NotificationName为key去查找对应的 Value,若找到value,则取出对应的value;若未找到对应的value,则新建一个 table,然后将这个table以NotificationName为key添加到Named Table中。 4. 若在保存Observation的table中,以object为key取对应的链表。若找到了则 直接在链接末尾插入之前实例化好的Observation;若未找到则以之前实例化好 的Observation对象作为头节点插入进去。 5. 如果没有通知名称: 直接根据object 去 NameLess表找 6. 如果没有名称,也没有object呢? 直接去链表尾部插入 发送通知流程 发送通知一般调用`postNotificationName:object:userInfo:`来实现,内部会 根据传入的参数实例化一个NSNotification对象,包含name、object、userinfo 等信息 发送通知的流程总体来说是根据NotificationName和object找到对应的链表,然 后遍历整个链表,给每个Observation节点中保存的oberver发送对应的SEL消息 1. 首先会创建一个数组observerArray用来保存需要通知的observer。 2. 遍历wildcard链表,将observer添加到observerArray数组中。 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中。遍历observerArray数组,取出其中的 observer节点(包含了观察者对象和selector),调用形式如下: [o->observer performSelector: o->selector withObject: notification]; 哪个线程发送,哪个线程接收: 发送通知的时候直接在当前线程去发送消息,因此, 发送通知的线程和接收通知 的线程是同一线程 发送通知是同步的 顺序遍历,同步 移除通知流程 1. 若NotificationName和object都为nil,则清空wildcard链表。 2. 若NotificationName为nil,遍历named table,若object为nil,则清空 named table,若object不为nil,则以object为key找到对应的链表,然后清空 链表。在nameless table中以object为key找到对应的observer链表,然后清 空,若object也为nil,则清空nameless table。 3. 若NotificationName不为nil,在named table中以NotificationName为key 找到对应的table,若object为nil,则清空找到的table,若object不为nil,则以 object为key在找到的table中取出对应的链表,然后清空链表。 cache 哈希表 用于快速缓存 NSNotificationQueue typedef NS_ENUM(NSUInteger, NSPostingStyle) { NSPostWhenIdle = 1, // 当runloop处于空闲状态时post NSPostASAP = 2, // 当当前runloop完成之后立即post NSPostNow = 3 // 立即post }; NSNotification *notification = [NSNotification notificationWithName:@"111" object:nil]; [[NSNotificationQueue defaultQueue] enqueueNotification: notification postingStyle:NSPostASAP]; - NSPostingStyle为NSPostNow 模式是同步发送, - NSPostWhenIdle或者NSPostASAP是异步发送 NSNotificationQueue将通知添加到队列中时,其中postringStyle参数就是定义 通知调用和runloop状态之间关系。 指定主线程 使用block接口addObserverForName:object:queue:usingBlock:指定主线程 多次添加同一个通知 由于源码中并不会进行重复过滤,所以添加同一个通知,等于就是添加了2次, 回调也会触发两次 多次移除通知 多次移除,并没有问题,因为会去map中查找,找到才会删除。当name和 object都为nil时,会移除所有关于该observer的WILDCARD 子线程发送通知 crash 异常问题 你在子线程发通知,默认子线程执行,这时候如果主线程把收通知的self释放 了,子线程还未执行完的代码就会出问题 EXC_BAD_ACCESS 解决: 为了避免通知因为线程阻塞,最好是主线程发通知,需要耗时操作的逻辑在接收 到通知以后自己开线程处理。