问题: 简述一下Notification相关内容.


  • 实现原理(结构设计、通知如何存储的、name&observer&SEL之间的关系等)
  • 通知的发送时同步的,还是异步的
  • NSNotificationCenter接受消息和发送消息是在一个线程里吗?如何异步发送消息
  • NSNotificationQueue是异步还是同步发送?在哪个线程响应
  • NSNotificationQueue和runloop的关系
  • 如何保证通知接收的线程在主线程
  • 页面销毁时不移除通知会崩溃吗
  • 多次添加同一个通知会是什么结果?多次移除通知呢

问题: 简述一下Notification的实现原理.


Notification 相关的类主要有三个.

NSNotification : 通知的模型,内部包含 name 、 object 、userInfo.

NSNotificationCenter : 通知中心负责发送 NSNotification.

NSNotificationQueue : 通知队列,负责在某些时机调用Center发送post通知.


NSNotification 底层的存储结构如下代码所示.

// 根容器,NSNotificationCenter持有
typedef struct NSTal {
    Observation * wildcard; // 链表结构,保存既没有name也没有object的通知
    GSIMapTable nameless; // 保存只有object的通知
    GSIMapTable named; // 保存有name的通知(不管是否含有object)
} NSTable;

typedef struct Obs {
    id observer; // 观察者对象,接收通知的对象
    SEL selector; // 响应方法
    struct Obs *next; // 链表中得下个元素
} Observation;

上面的整体存储结构如下图所示.


问题: 通知的发送是同步的,还是异步的


通知的发送是同步的,在哪一个线程发送就在哪一个线程接收,并没有开启异步线程.通知的异步发送实际上是利用RunLoop机制进行的延时发送,并不是真正意义上的异步线程发送通知.


问题: NSNotificationCenter接受消息和发送消息是在一个线程里吗?如何异步发送消息


NSNotificationCenter接受消息和发送消息是在一个线程里,想要异步发送消息,那就需要开启一个异步线程,在此线程中进行消息的发送.


问题: NSNotificationQueue是异步还是同步发送?在哪个线程响应


NSNotificationQueue是同步发送,它的异步发送更准确的说是利用RunLoop机制做的延时发送.同时消息在哪一个线程发送就在哪一个线程响应.

NSNotificationQueue的发送时机如下所示.

typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1, // runloop空闲时发送
    NSPostASAP = 2, // 尽快发送,这种时机是穿插在每次事件完成期间来做的
    NSPostNow = 3, // 立刻发送或者合并通知完成之后发送
};

问: NSNotificationQueue和runloop的关系


NSNotificationQueue与runloop的关系是紧密相连的,NSNotificationQueue的发送时机是依赖runloop实现的.


问: 如何保证通知接收的线程在主线程?


  1. 在主线程中进行发送消息即可.

  2. 如果是在主线程中响应异步线程的通知,可用如下的API.

    - (id <NSObject>)addObserverForName:(nullable NSNotificationName)name 
                                 object:(nullable id)obj 
                                  queue:(nullable NSOperationQueue *)queue 
                             usingBlock:(void (^)(NSNotification *note))block
    
  3. NSMachPort 方式.解决主线程中响应异步线程的通知.

    通过在主线程的RunLoop添加machPort,设置这个port的delegate. 通过这个port, 其他线程可与主线程进行通信. 在这个port的代理方法中执行的代码肯定在主线程中运行,所以在代理方法中 NSNotificationCenter调用post方法即可.


问: 页面销毁时不移除通知会崩溃吗?


在iOS9之前, 观察中心Center对观察者的引用是unsafe_unretained; 在iOS9之后,观察中心Center对观察者的引用是weak.

unsafe_unretained 是不安全的. 如果对象释放,unsafe_unretained指针是不会置为nil的,所以会造成崩溃问题.

但是 weak 在对象释放时,weak修饰的指针会被置为nil,不会造成崩溃.


问: 多次添加同一个通知会是什么结果?多次移除通知呢


多次添加同一通知,会导致一次发送多次响应. 多次移除通知不会造成crash问题.


问: 下面的方式能接收到通知吗?为什么

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

不能,这个是因为 Notification 的存储逻辑导致. 我们可以看到 观察者添加通知时,同时存在name和object,所以存放的位置为 NSNotificationCenter的 named 中.

但是在发送通知时,只有 name, 没有object. 这样是会导致查找失败,最终会导致调用失败.


IT界无底坑洞栋主 欢迎加Q骚扰:676758285