NSNotification详解

作者: 码小菜 | 来源:发表于2019-04-23 17:42 被阅读2次

目录
一,基本概念
二,NSNotification
三,NSNotificationCenter
四,NSNotificationQueue
五,多线程
六,同步与异步
七,实现原理
八,流程分析

一,基本概念

  • 用途:跨层通信

  • 特点
    1,耦合低(发送者和观察者无需知道对方的具体信息)
    2,多对多(一个通知可以被多个对象监听,一个对象可以监听多个通知)

  • 使用注意点
    1,通知的名称要规范,便于区分通知的具体目的
    2,如果在回调中需要更新UI,要注意是否在主线程中执行
    3,如果通知较多,最好用表格对发送者和观察者进行统一管理,当出现问题时方便查找

二,NSNotification

  • 作用:封装通知的基本信息(nameobjectuserInfo
// 通知的名称
@property (readonly, copy) NSNotificationName name;
// 发出通知的对象
@property (nullable, readonly, retain) id object;
// 给观察者传递的数据
@property (nullable, readonly, copy) NSDictionary *userInfo;

三,NSNotificationCenter

  • 作用:管理整个通知的流程(addObserverpostNotificationremoveObserver
// 全局只有一个
@property (class, readonly, strong) NSNotificationCenter *defaultCenter;
// 可以指定收到通知的回调在哪个队列中执行
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;
  • 问题:需要在dealloc中调用removeObserver吗?

答:
1,在iOS9之前需要,iOS9开始不需要
2,iOS9之前通知中心对观察者是unsafe_unretained引用,当观察者释放后,unsafe_unretained指针不会被置为nil,unsafe_unretained就变成了野指针,如果再次收到通知会引起野指针crash
3,iOS9开始改成了weakweak会自动被置为nil,不会出现野指针crash

四,NSNotificationQueue

  • 定义:每个线程都有一个默认的NSNotificationQueue,并且都与NSNotificationCenter关联在一起,它是NSNotificationCenter的缓冲池,遵循先进先出的原则,会把排在最前面的NSNotification发送给NSNotificationCenter
// 当前线程的默认队列
@property (class, readonly, strong) NSNotificationQueue *defaultQueue;
  • 作用

1,控制通知何时发出

typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1, // 在当前runloop空闲时发出
    NSPostASAP = 2, // as soon as possible,尽快发出
    NSPostNow = 3 // 通知合并后立即发出
};

2,控制通知如何合并

// 相同的name或者object只会发出一个
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
    NSNotificationNoCoalescing = 0, // 不合并
    NSNotificationCoalescingOnName = 1, // 按照name合并
    NSNotificationCoalescingOnSender = 2 // 按照object合并
};
// 添加观察者
[[NSNotificationCenter defaultCenter] addObserverForName:@"NotificationName"
                                                  object:nil
                                                   queue:NSOperationQueue.mainQueue
                                              usingBlock:^(NSNotification * _Nonnull note) {
                                                  // 收到通知的回调
                                                  NSLog(@"receivedNotification");
                                              }];
// 创建通知
NSNotification *notification = [[NSNotification alloc] initWithName:@"NotificationName"
                                                             object:nil
                                                           userInfo:nil];
// 添加通知到队列中
[[NSNotificationQueue defaultQueue] enqueueNotification:notification
                                           postingStyle:NSPostWhenIdle
                                           coalesceMask:NSNotificationNoCoalescing // 不合并
                                               forModes:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification
                                           postingStyle:NSPostWhenIdle
                                           coalesceMask:NSNotificationNoCoalescing // 不合并
                                               forModes:nil];

// 打印结果
receivedNotification
receivedNotification

// 将“不合并”改为“按name合并”

// 打印结果
receivedNotification

3,设置mode:当前线程runloop在指定的mode下,NSNotificationQueue才能将NSNotification发送给NSNotificationCenter

五,多线程

  • 定义:通知在哪个线程中发出,收到通知的回调就在哪个线程中执行
[[NSNotificationCenter defaultCenter] addObserverForName:@"NotificationName"
                                                  object:nil
                                                   queue:nil
                                              usingBlock:^(NSNotification * _Nonnull note) {
                                                  // 收到通知的线程
                                                  NSLog(@"received---%@", NSThread.currentThread);
                                              }];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 发出通知的线程
    NSLog(@"post---%@", NSThread.currentThread);
    [[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationName"
                                                        object:nil
                                                      userInfo:nil];
});

// 打印结果
post---<NSThread: 0x60000126f680>{number = 3, name = (null)}
received---<NSThread: 0x60000126f680>{number = 3, name = (null)}
  • 问题:一般收到通知都需要更新UI,如何保证收到通知的回调在主线程中执行?
// 方法一:在回调中强制切换到主线程
dispatch_async(dispatch_get_main_queue(), ^{
     // 更新UI
});

// 方法二:设置回调在主队列中执行
[[NSNotificationCenter defaultCenter] addObserverForName:@"NotificationName"
                                                  object:nil
                                                   queue:NSOperationQueue.mainQueue
                                              usingBlock:^(NSNotification * _Nonnull note) {
                                                   // 更新UI
                                              }];

六,同步与异步

  • 同步:通知发出后必须等待收到通知的回调执行完毕才会往下执行(postNotification方法是同步执行的)
[[NSNotificationCenter defaultCenter] addObserverForName:@"NotificationName"
                                                  object:nil
                                                   queue:nil
                                              usingBlock:^(NSNotification * _Nonnull note) {
                                                  sleep(3);
                                                  NSLog(@"222");
                                              }];

[[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationName"
                                                    object:nil
                                                  userInfo:nil];
NSLog(@"111");

// 打印结果
222
111
  • 异步:通知发出就会往下执行,无需等待收到通知的回调执行完毕(enqueueNotification方法是异步执行的,如果postingStyleNSPostNow那还是同步)
[[NSNotificationCenter defaultCenter] addObserverForName:@"NotificationName"
                                                  object:nil
                                                   queue:nil
                                              usingBlock:^(NSNotification * _Nonnull note) {
                                                  sleep(3);
                                                  NSLog(@"222");
                                              }];

NSNotification *notification = [[NSNotification alloc] initWithName:@"NotificationName"
                                                             object:nil
                                                           userInfo:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification
                                           postingStyle:NSPostWhenIdle];
NSLog(@"111");

// 打印结果
111
222

七,实现原理

  • NSNotificationCenter内部定义了两个结构体
// 保存name,object与观察者的对应关系
typedef struct NCTable {
  MapTable named; // 保存有传入name的观察者
  MapTable nameless; // 保存没有传入name但有传入object的观察者
  struct Observation *wildcard; // 保存name和object都没有传入的观察者
};
// 保存观察者信息
typedef struct Observation {
  id observer; // 保存添加观察者时传入的observer
  SEL selector; // 保存添加观察者时传入的selector
  struct Observation *next; // 保存监听同一个通知的下一个观察者
};

// 所有添加观察者的方法最后都会调用addObserver:selector:name:object:方法
  • Named MapTable
    1,名称为name的通知可以由多个object发出,由object发出的名称为name的通知可以有多个观察者,多个观察者用链表来保存
    2,如果object传nil,系统会自动生成一个key,这个key对应的value(链表)上的观察者都会监听所有名称为name的通知,不论是由哪个object发出的
Named MapTable.png
  • Nameless MapTable
    因为没有name,所以少了一层MapTable
Nameless MapTable.png
  • Wildcard链表
    1,没有MapTable,直接用链表保存
    2,nameobject都没有传入的观察者,会监听所有的通知
Wildcard链表.png

八,流程分析

以有传入name和object为例,其他过程类似

  • 添加观察者

1,在初始化NSNotificationCenter时创建一个NCTable
2,根据addObserver方法传入的参数实例化一个Observation
3,根据是否传入name和object选择添加在named MapTablenameless MapTable还是在wildcard链表
4,以name为key在大MapTable中查找对应的value(小MapTable),如果没有就创建新的小MapTable并添加到大MapTable
5,以object为key在小MapTable中查找对应的value(链表),如果找到就把Observation插到链表末尾,如果没有找到就先创建一个头结点然后再插入

  • 发出通知

1,postNotification方法内部实例化一个NSNotification对象来保存传入的各种参数
2,创建一个数组ObservationArray
3,遍历wildcard链表,把所有observation都添加到ObservationArray中(wildcard链表中的观察者会监听所有的通知)
4,以name和object为key查找对应的链表,也将其所有的observation都添加到ObservationArray中
5,遍历ObservationArray,让每个observation都执行如下的代码

// 让observer调用selector并传入notification
[observation->observer performSelector: observation->selector withObject: notification];
  • 移除观察者

1,根据name和object找到观察者所在的链表
2,根据observer在链表中找到对应的observation并删除

相关文章

网友评论

    本文标题:NSNotification详解

    本文链接:https://www.haomeiwen.com/subject/gsodgqtx.html