一、通知的特性
NSNotification是苹果提供的一种”同步“单向且线程安全的消息通知机制(并且消息可以携带信息),观察者通过向单例的通知中心注册消息,即可接收指定对象或者其他任何对象发来的消息,可以实现”单播“或者”广播“消息机制,并且观察者和接收者可以完全解耦实现跨层消息传递;
同步:消息发送需要等待观察者处理完成消息后再继续执行;
单向:发送者只发送消息,接收者不需要回复消息;
线程安全:消息发送及接收都是在同一个线性完成,不需要处理线程同步问题,所以通知有子线程更新UI的风险,不确定线程时,最好使用方式2指定线程执行通知回调;
NSNotificationCenter消息通知中心,全局单例模式(每个进程都默认有一个默认的通知中心,用于进程内通信)
NSNotificationQueue通知队列实现了通知消息的管理,如消息发送时机、消息合并策略,并且为先入先出方式管理消息,但实际消息发送仍然是通过NSNotificationCenter通知中心完成;
二、通知的特点
是使用观察者模式来实现的用于跨层传递消息的机制;
传递方式为一对多;
什么情况下使用通知?
观察者模式:定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得道通知并自动更新。
三、通知相关的方法
NSNotification包含了消息发送的一些信息,包括name消息名称、object消息发送者、observer消息观察者、userinfo消息发送者携带的额外信息。
1、向观察者中心添加观察者
方式1:观察者接收到通知后执行任务的代码在发送通知的线程中执行
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
方式2:观察者接受到通知后执行任务的代码在指定的操作队列中执行
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block
2、通知中心向观察者发送消息
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
3、移除观察者
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;
五、通知实现
1、向观察者中心添加观察者
ViewController.m
///向观察者中心添加观察者
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getNotificationAction:) name:@"postNSNotification" object:nil];
///不指定线程处理信息-发送通知与接收通知-为同一线程
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getNotificationTreadAction:) name:@"postNSNotificationTread" object:nil];
///指定主线程处理回调信息
self.objectN = [[NSNotificationCenter defaultCenter] addObserverForName:@"postNSNotificationTread"object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
NSDictionary * infoDic = [note object];
if (![infoDic isKindOfClass:[NSDictionary class]]) {
return;
}
NSLog(@"指定线程---%@",[NSThread currentThread]);
NSLog(@"指定线程---%@",infoDic);
}];
notificationViewController * vc = [[notificationViewController alloc]init];
[self presentViewController:vc animated:YES completion:nil];
///通知回调方法
- (void)getNotificationAction:(NSNotification *)notification{
NSDictionary * infoDic = [notification object];
if (![infoDic isKindOfClass:[NSDictionary class]]) {
return;
}
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%@",infoDic);
}
- (void)getNotificationTreadAction:(NSNotification *)notification{
NSDictionary * infoDic = [notification object];
if (![infoDic isKindOfClass:[NSDictionary class]]) {
return;
}
NSLog(@"不指定线程---%@",[NSThread currentThread]);
NSLog(@"不指定线程---%@",infoDic);
}
3、移除观察者
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"postNSNotification" object:nil];
if (self.objectN) {
[[NSNotificationCenter defaultCenter] removeObserver:self.objectN];
}
}
2、通知中心向观察者发送消息
NSMutableDictionary * dic = [NSMutableDictionary dictionary];
[dic setValue:@"通知-传值" forKey:@"data"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"postNSNotification" object:dic];
///开启子线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"开启子线程---%@",[NSThread currentThread]);
NSMutableDictionary * dic2 = [NSMutableDictionary dictionary];
[dic2 setValue:@"Tread子线程通知-传值" forKey:@"data"];
///发送通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"postNSNotificationTread" object:dic2];
});
4、打印信息
2021-04-26 14:20:40.235845+0800 ocProjectDemo[43195:1399033] <NSThread: 0x600003170900>{number = 1, name = main}
2021-04-26 14:20:40.236115+0800 ocProjectDemo[43195:1399033] {
data = "\U901a\U77e5-\U4f20\U503c";
}
2021-04-26 14:20:40.236424+0800 ocProjectDemo[43195:1399109] 开启子线程---<NSThread: 0x6000031303c0>{number = 5, name = (null)}
2021-04-26 14:20:40.236650+0800 ocProjectDemo[43195:1399109] 不指定线程---<NSThread: 0x6000031303c0>{number = 5, name = (null)}
2021-04-26 14:20:40.236996+0800 ocProjectDemo[43195:1399109] 不指定线程---{
data = "Tread\U5b50\U7ebf\U7a0b\U901a\U77e5-\U4f20\U503c";
}
2021-04-26 14:20:40.237510+0800 ocProjectDemo[43195:1399033] 指定线程---<NSThread: 0x600003170900>{number = 1, name = main}
2021-04-26 14:20:40.237674+0800 ocProjectDemo[43195:1399033] 指定线程---{
data = "Tread\U5b50\U7ebf\U7a0b\U901a\U77e5-\U4f20\U503c";
}
六、通知机制
20200820112524809.png首先,信息的传递就依靠通知(NSNotification),也就是说,通知就是信息(执行的方法,观察者本身(self),参数)的包装。通知中心(NSNotificationCenter)是个单例,向通知中心注册观察者,也就是说,这个通知中心有个集合,这个集合存放着观察者。发送通知需要name参数,添加观察者也有个name参数,这两个name一样的时候,当发送通知时候,观察者对象就能接收到信息,执行对应的操作。
六、通知原理解析
通知全局对象表结构如下:
typedef struct NCTbl {
Observation *wildcard; /* Get ALL messages*/
GSIMapTable nameless; /* Get messages for any name.*/
GSIMapTable named; /* Getting named messages only.*/
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;
其中数据结构中重要的是两张GSIMapTable表:named、naeless,及单链表wildcard;
named,保存着传入通知名称的通知hash表;
nameless,保存没有传入通知名称的hash表;
wildcard,保存既没有通知名称又没有传入object的通知单链表;
保存含有通知名称的通知表named需要注册object对象,因此该表结构体通过传入的name作为key,其中value同时也为GSIMapTable表用于存储对应的object对象的observer对象;
对没有传入通知名称只传入object对象的通知表nameless而言,只需要保存object与observer的对应关系,因此object作为key用observer作为value;
添加观察者
具体的添加观察者的核心函数(block形式只是该函数的包装)大致代码如下:
- (void) addObserver: (id)observer
selector: (SEL)selector
name: (NSString*)name
object: (id)object
{
Observation *list;
Observation *o;
GSIMapTable m;
GSIMapNode n;
//入参检查异常处理
...
//table加锁保持数据一致性
lockNCTable(TABLE);
//创建Observation对象包装相应的调用函数
o = obsNew(TABLE, selector, observer);
//处理存在通知名称的情况
if (name)
{
//table表中获取相应name的节点
n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
if (n == 0)
{
//未找到相应的节点,则创建内部GSIMapTable表,以name作为key添加到talbe中
m = mapNew(TABLE);
name = [name copyWithZone: NSDefaultMallocZone()];
GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
GS_CONSUMED(name)
}
else
{
//找到则直接获取相应的内部table
m = (GSIMapTable)n->value.ptr;
}
//内部table表中获取相应object对象作为key的节点
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
if (n == 0)
{
//不存在此节点,则直接添加observer对象到table中
o->next = ENDOBS;//单链表observer末尾指向ENDOBS
GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
}
else
{
//存在此节点,则获取并将obsever添加到单链表observer中
list = (Observation*)n->value.ptr;
o->next = list->next;
list->next = o;
}
}
//只有观察者对象情况
else if (object)
{
//获取对应object的table
n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
if (n == 0)
{
//未找到对应object key的节点,则直接添加observergnustep-base-1.25.0
o->next = ENDOBS;
GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
}
else
{
//找到相应的节点则直接添加到链表中
list = (Observation*)n->value.ptr;
o->next = list->next;
list->next = o;
}
}
//处理即没有通知名称也没有观察者对象的情况
else
{
//添加到单链表中
o->next = WILDCARD;
WILDCARD = o;
}
//解锁
unlockNCTable(TABLE);
}
对于block形式代码如下:
- (id) addObserverForName: (NSString *)name
object: (id)object
queue: (NSOperationQueue *)queue
usingBlock: (GSNotificationBlock)block
{
GSNotificationObserver *observer =
[[GSNotificationObserver alloc] initWithQueue: queue block: block];
[self addObserver: observer
selector: @selector(didReceiveNotification:)
name: name
object: object];
return observer;
}
- (id) initWithQueue: (NSOperationQueue *)queue
block: (GSNotificationBlock)block
{
self = [super init];
if (self == nil)
return nil;
ASSIGN(_queue, queue);
_block = Block_copy(block);
return self;
}
- (void) didReceiveNotification: (NSNotification *)notif
{
if (_queue != nil)
{
GSNotificationBlockOperation *op = [[GSNotificationBlockOperation alloc]
initWithNotification: notif block: _block];
[_queue addOperation: op];
}
else
{
CALL_BLOCK(_block, notif);
}
}
对于block形式通过创建GSNotificationObserver对象,该对象会通过Block_copy拷贝block,并确定通知操作队列,通知的接收处理函数didReceiveNotification中是通过addOperation来实现指定操作队列处理,否则直接执行block;
发送通知的核心函数大致逻辑如下:
- (void) _postAndRelease: (NSNotification*)notification
{
//入参检查校验
//创建存储所有匹配通知的数组GSIArray
//加锁table避免数据一致性问题
//获取所有WILDCARD中的通知并添加到数组中
//查找NAMELESS表中指定对应观察者对象object的通知并添加到数组中
//查找NAMED表中相应的通知并添加到数组中
//解锁table
//遍历整个数组并依次调用performSelector:withObject处理通知消息发送
//解锁table并释放资源
}
上面发送的重点就是获取所有匹配的通知,并通过performSelector:withObject发送通知消息,因此通知发送和接收通知的线程是同一个线程(block形式通过操作队列来指定队列处理);
补充:
在类方法里,添加的观察者为类,回调执行的也必须是类方法
参考
https://www.jianshu.com/p/c8ed0884fa16
https://blog.csdn.net/xiaoxiaobukuang/article/details/108120044
网友评论