美文网首页KVO与Notif通知
通知(NSNotification)

通知(NSNotification)

作者: 青菜白玉堂 | 来源:发表于2021-04-26 14:21 被阅读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

    相关文章

      网友评论

        本文标题:通知(NSNotification)

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