iOS NSNotification相关

作者: 朝雨晚风 | 来源:发表于2021-08-17 15:24 被阅读0次

    1、通知实现原理(结构设计、通知如何存储的、name&observer&SEL之间的关系等)

    • NSNotification 通知的模型 name、object、userinfo.
    • NSNotificationCenter通知中心 负责发送NSNotification
    • NSNotificationQueue通知队列 负责在某些时机触发 调用NSNotificationCenter通知中心 post通知

    通知是结构体通过双向链表进行数据存储

    // 根容器,NSNotificationCenter持有
    typedef struct NCTbl {
      Observation       *wildcard;  /* 链表结构,保存既没有name也没有object的通知 */
      GSIMapTable       nameless;   /* 存储没有name但是有object的通知 */
      GSIMapTable       named;      /* 存储带有name的通知,不管有没有object  */
        ...
    } NCTable;
    
    // Observation 存储观察者和响应结构体,基本的存储单元
    typedef struct  Obs {
      id        observer;   /* 观察者,接收通知的对象  */
      SEL       selector;   /* 响应方法     */
      struct Obs    *next;      /* Next item in linked list.    */
      ...
    } Observation;
    
    namelsee name

    主要是以key value的形式存储,这里需要重点强调一下 通知以 name和object两个纬度来存储相关通知内容,也就是我们添加通知的时候传入的两个不同的方法.
    简单理解name&observer&SEL之间的关系就是name作为key, observer作为观察者对象,当合适时机触发就会调用observer的SEL.

    2、通知的添加方式

    [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(receiceNotification:)
                                                     name:@"JKRNO"
                                                   object:nil];
    
    

    addObserver:接收通知的对象
    selector:接收通知的对象接收到通知调用的方法
    name:通知的名字
    object:接收哪个对象发送的通知

    @property (nonatomic, strong) NSObject *observer;
    ...
        self.observer = [[NSNotificationCenter defaultCenter]
                        addObserverForName:@"JKRSEC"
                        object:self 
                        queue:[NSOperationQueue new]
                        usingBlock:^(NSNotification * _Nonnull note) {
                            /// 接收到通知回调的block
                        }];
    
    

    返回值:通知实际添加到的observer,移除通知要移除这个对象
    name参数:通知的名字
    object:接收哪个对象发送的通知
    queue:接收到通知的回调在哪个线程中调用,如果传mainQueue则通知在主线程回调,否则在子线程回调
    usingBlock:接收到通知回调的block

    3、通知的移除 页面销毁时不移除通知会崩溃吗?

    1. addObserver添加的通知在iOS 9.0之前,通知中心对观察者对象进行unsafe_unretained 引用,当被引用的对象释放时不会自动置为nil,,也就是成了野指针,需要在dealloc手动移除。
      iOS 9.0之后通知中心对观察者做了弱引用,当被添加通知的对象销毁的时候,通知会自动被移除。。
    2. 但 addObserverForName,被系统 retain,手动移除通知,同时这个 block类型参数也需注意避免循环引用。最明显的体现就是,就算你的ViewController被释放了,走了dealloc,第二次进入VC中会执行两次block中的代码块。

    4、通知的发送时同步的,还是异步的?

    - (void)textNotifation {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notifyHandle) name:@"NotificationTestName" object:nil];
        NSLog(@"即将发出通知");
        [[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationTestName" object:nil];
        NSLog(@"处理发出通知的下一条代码");
    }
    
    - (void)notifyHandle {
        sleep(10);
        NSLog(@"通知处理结束");
    }
    

    以上示例证明通知的发送和接收和同步的,即通知发送后,在通知接收方法完成之前,通知发送之后的代码会等待执行
    默认在哪个线程发送通知,就在哪个线程接收到。

    5、如何让通知异步的方法?

    1、将通知的发送放到子线程中

     NSLog(@"即将发出通知");
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationTestName" object:nil];
        });
        NSLog(@"处理发出通知的下一条代码");
    

    2、将通知的处理方法放到子线程中

    - (void)notifyHandle {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(10);
            NSLog(@"通知处理结束");
        });
    }
    

    3、通知的发送可以添加到NSNotificationQueue异步通知缓冲队列中

        NSLog(@"即将发出通知");
        NSNotification *notification = [NSNotification notificationWithName:XYNotificationTestName object:nil];
        [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];
        NSLog(@"处理发出通知的下一条代码");
    

    NSNoticicationQueue是一个通知缓冲队列,以FIFO(先进先出)的规则维护通知队列的发送。
    向通知队列中添加通知有三种枚举类型:
    1,NSPostWhenIdle:runloop空闲的时候回调通知方法
    2,NSPostASAP:runloop能够调用的时候就回调通知方法
    3,NSPostNow:runloop立即回调通知方法

    • 1、接收通知的线程,和发送通知所处的线程是同一个线程,和在哪个线程注册通知无关。
    • 2、遍历observerArray数组,取出其中的observer节点[o->observer performSelector: o->selector withObject: notification],所以通知是同步处理的机制。
    • 3、如果想改同步为异步,在收到通知方法中新开辟一个线程处理事件。
    • 4、如果在子线程接受通知并更新UI 会造成crash Main Thread Checker: UI API called on a background thread: -[UIButton setTitle:forState:],要么回到主线程update UI,要么在主线程发送通知

    6、下面的方式能接收到通知吗?多次添加同一个通知会是什么结果?多次移除通知呢?

    // 监听通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1];
    // 发送通知
    [NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];
    
    • 1、如果发送的通知指定了object对象,那么观察者接收的通知设置的object对象与其一样,才会接收到通知,
    • 2、但是接收通知如果将这个参数设置为了nil,则会接收一切通知。
    • 3、多次添加同一个通知会触发多次调用,多次移除通知也不会crash

    7、如何保证通知接收的线程在主线程?

    dispatch_async(dispatch_get_main_queue(), ^{
                    [[NSNotificationCenter defaultCenter] postNotificationName: @"NotificationTestName"  object:self];
                });
    
    - (void)notifyHandle {
        dispatch_async(dispatch_get_main_queue(), ^{
            sleep(10);
            NSLog(@"通知处理结束");
        });
    }
    

    如果在子线程接受通知并更新UI 会造成crash Main Thread Checker: UI API called on a background thread: -[UIButton setTitle:forState:],要么回到主线程update UI,要么在主线程发送通知

    8、NSNotificationQueue和runloop的关系?

    为了验证通知和runloop的关系,在主线程添加runloop的状态监听:
    postringStyle参数就是定义通知调用和runloop状态之间关系。
    该参数的三个可选参数:
    1,NSPostWhenIdle:runloop空闲的时候回调通知方法
    2,NSPostASAP:runloop能够调用的时候就回调通知方法
    3,NSPostNow:runloop立即回调通知方法

    - (void)runLoopNotification {
        CFRunLoopRef runloop = CFRunLoopGetCurrent();
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            switch (activity) {
                case kCFRunLoopEntry:
                    NSLog(@"进入runLoop");
                    break;
                case kCFRunLoopBeforeTimers:
                    NSLog(@"处理timer事件");
                    break;
                case kCFRunLoopBeforeSources:
                    NSLog(@"处理source事件");
                    break;
                case kCFRunLoopBeforeWaiting:
                    NSLog(@"进入睡眠");
                    break;
                case kCFRunLoopAfterWaiting:
                    NSLog(@"被唤醒");
                    break;
                case kCFRunLoopExit:
                    NSLog(@"退出");
                    break;
                default:
                    break;
            }
        });
        CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
        CFRelease(observer);
        [self addObserverForNotify];
    }
    - (void)addObserverForNotify {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(receiceNotification:)
                                                     name:@"JKRNO"
                                                   object:nil];
        [self postNotification];
    }
    - (void)postNotification {
        NSLog(@"1");
        NSNotification *notification = [NSNotification notificationWithName:@"JKRNO" object:nil];
        [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle];
        NSLog(@"3");
    }
    
    - (void)receiceNotification:(NSNotification *)notification {
        sleep(3);
        NSLog(@"2");
    }
    

    用第一个参数NSPostWhenIdle的时候,通知发送的时候runloop和通知方法调用的顺序如下:可以看到,通知回调方法是等待到当下线程runloop进入睡眠状态才会调用。

    2021-08-17 14:27:46.237979+0800 exercise[30888:2524325] 1
    2021-08-17 14:27:46.238169+0800 exercise[30888:2524325] 3
    2021-08-17 14:27:46.264124+0800 exercise[30888:2524325] 处理timer事件
    2021-08-17 14:27:46.264326+0800 exercise[30888:2524325] 处理source事件
    2021-08-17 14:27:46.264705+0800 exercise[30888:2524325] 处理timer事件
    2021-08-17 14:27:46.264828+0800 exercise[30888:2524325] 处理source事件
    2021-08-17 14:27:46.265067+0800 exercise[30888:2524325] 进入睡眠
    2021-08-17 14:27:49.266005+0800 exercise[30888:2524325] 2
    2021-08-17 14:27:49.267331+0800 exercise[30888:2524325] 被唤醒
    

    用第二个参数NSPostASAP的时候,通知发送的时候runloop和通知方法调用的顺序:可以看到,通知回调方法是等待到当下线程runloop开始接收事件源的时候就会调用。

    2021-08-17 14:26:37.309870+0800 exercise[30867:2523325] 1
    2021-08-17 14:26:37.310111+0800 exercise[30867:2523325] 3
    2021-08-17 14:26:37.337183+0800 exercise[30867:2523325] 处理timer事件
    2021-08-17 14:26:40.337853+0800 exercise[30867:2523325] 2
    2021-08-17 14:26:40.338126+0800 exercise[30867:2523325] 处理source事件
    2021-08-17 14:26:40.338555+0800 exercise[30867:2523325] 处理timer事件
    2021-08-17 14:26:40.338684+0800 exercise[30867:2523325] 处理source事件
    2021-08-17 14:26:40.339368+0800 exercise[30867:2523325] 处理timer事件
    2021-08-17 14:26:40.339502+0800 exercise[30867:2523325] 处理source事件
    2021-08-17 14:26:40.339652+0800 exercise[30867:2523325] 进入睡眠
    

    用第三个参数NSPostNow的时候,通知发送的时候runloop和通知方法调用的顺序:其实和直接用默认的通知中心添加通知是一样的,通知马上调用回调方法。

    2021-08-17 14:28:48.991694+0800 exercise[30907:2525314] 1
    2021-08-17 14:28:51.992326+0800 exercise[30907:2525314] 2
    2021-08-17 14:28:51.992535+0800 exercise[30907:2525314] 3
    2021-08-17 14:28:52.022365+0800 exercise[30907:2525314] 处理timer事件
    2021-08-17 14:28:52.022576+0800 exercise[30907:2525314] 处理source事件
    2021-08-17 14:28:52.023646+0800 exercise[30907:2525314] 处理timer事件
    2021-08-17 14:28:52.023812+0800 exercise[30907:2525314] 处理source事件
    2021-08-17 14:28:52.023986+0800 exercise[30907:2525314] 进入睡眠
    

    相关文章

      网友评论

        本文标题:iOS NSNotification相关

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