学习笔记:原理篇-NSNotification

一、概述

  • 通知经典的使用场景是多对多的场景。

  • 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;

其中MapTablekey-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通知机制

你可能感兴趣的:(学习笔记:原理篇-NSNotification)