KVO与Notif通知

通知(NSNotification)

2021-04-26  本文已影响0人  青菜白玉堂

一、通知的特性

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

上一篇 下一篇

猜你喜欢

热点阅读