目录
一,基本概念
二,NSNotification
三,NSNotificationCenter
四,NSNotificationQueue
五,多线程
六,同步与异步
七,底层实现
八,流程分析
一,基本概念
用途:跨层通信
特点
1,耦合低(发送者和观察者无需知道对方的具体信息)
2,多对多(一个通知可以被多个对象监听,一个对象可以监听多个通知)使用注意点
1,通知的名称要规范,便于区分通知的具体目的
2,如果在回调中需要更新UI,要注意是否在主线程中执行
3,如果通知较多,最好用表格对发送者和观察者进行统一管理,当出现问题时方便查找
二,NSNotification
- 作用:封装通知的基本信息(
name
,object
,userInfo
)
// 通知的名称
@property (readonly, copy) NSNotificationName name;
// 发出通知的对象
@property (nullable, readonly, retain) id object;
// 给观察者传递的数据
@property (nullable, readonly, copy) NSDictionary *userInfo;
三,NSNotificationCenter
- 作用:管理整个通知的流程(
addObserver
,postNotification
,removeObserver
)
// 全局只有一个
@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之前通知中心对观察者是unsafe_unretained
引用,当观察者释放后,unsafe_unretained
不会自动被置为nil而变成野指针,如果再次向观察者发送通知会导致crash
2,iOS9之后不需要,iOS9之后改成了weak
,weak
会自动被置为nil,不会出现野指针crash
3,如果用addObserverForName:object:queue:usingBlock
来添加观察者,那么在iOS9之后仍然需要,此时通知中心对观察者是strong
引用
四,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
// 将“NSNotificationNoCoalescing”改为“NSNotificationCoalescingOnName”
// 打印结果
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
方法是异步执行的,如果postingStyle
传NSPostNow
那还是同步)
[[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
发出的
- Nameless MapTable
因为没有name
,所以少了一层MapTable
- Wildcard链表
1,没有MapTable,直接用链表保存
2,name
和object
都没有传入的观察者,会监听所有的通知
八,流程分析
以有传入name和object为例,其他过程类似
-
添加观察者
1,在初始化NSNotificationCenter时创建一个NCTable
2,根据addObserver方法传入的参数实例化一个Observation
3,根据是否传入name和object选择添加在named MapTable
,nameless 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并删除