NSNotificationCenter 和 NSNotificationQueue 使用

NSNotificationCenter 和 NSNotificationQueue

通知中心和通知队列,需要明确的是队列只是缓存通知,并不实际推送通知,但是根据 NSPostingStyleNSNotificationCoalescing 的选择,通知队列可以决定向通知中心传递通知的时机以及是否合并相关的通知。

默认通知队列

NSNotificationQueue 提供了一个类属性 defaultQueue 来获取当前线程默认的通知队列,该队列相关联的通知中心是应用默认的通知中心,并且提供了下面的方法来向队列中添加通知。

- (void)enqueueNotification:(NSNotification *)notification
               postingStyle:(NSPostingStyle)postingStyle
               coalesceMask:(NSNotificationCoalescing)coalesceMask
                   forModes:(nullable NSArray<NSRunLoopMode> *)modes;

在使用这个方法时,需要知道的是,使用 NSNotificationCenter 直接推送通知时,通知的执行线程是推送通知的线程,知道这一点对通知队列的理解和使用十分重要。

因为使用上面的方法向队列中添加通知时,涉及到运行循环的模式,而运行循环在子线程中是默认关闭的。所以,除非是在主线程中调用上面的方法,否则需要注意子线程的运行循环是否开启,当然,如果使用 NSPostNow 来表示立即推送通知,那么这和主动调用 NSNotificationCenter 的推送方法效果一样,也不需要考虑运行循环的模式。

若是使用 NSPostASAP(as soon as possible) 和 NSPostWhenIdle 来延后通知的推送,那么就需要考虑到当前线程的运行循环是否开启,以及其是否处于预期的循环模式中。

如下面的例子,如果不开启运行循环,或者开启循环的模式与添加通知时指定的模式不一致时,通知都不会发出。

- (void)viewDidLoad {
    [super viewDidLoad];
     
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(test:) name:@"test" object:nil];
}

- (void)btnClick {
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSNotificationQueue *queue = [NSNotificationQueue defaultQueue];
        NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);
        [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
            
            NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);
        }];
        NSNotification *notification = [NSNotification notificationWithName:@"test" object:self];
        [queue enqueueNotification:notification postingStyle:NSPostASAP];
        notification = [NSNotification notificationWithName:@"test" object:nil];
		  /*
        [queue enqueueNotification:notification postingStyle:NSPostASAP];
        
        [queue enqueueNotification:notification
                      postingStyle:NSPostASAP
                      coalesceMask:NSNotificationCoalescingOnName|NSNotificationCoalescingOnSender
                          forModes:@[NSRunLoopCommonModes]];
		  */
        [queue enqueueNotification:notification
                      postingStyle:NSPostWhenIdle
                      coalesceMask:NSNotificationCoalescingOnName
                          forModes:@[NSRunLoopCommonModes]];
                
        [NSRunLoop.currentRunLoop run];
        
        /*
        NSTimer *timer = [NSTimer timerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);
        }];
        [NSRunLoop.currentRunLoop addTimer:timer forMode:@"MyRunloopMode"];
        
        [NSRunLoop.currentRunLoop runMode:@"MyRunloopMode" beforeDate:NSDate.distantFuture];
        */
    });
}

- (void)test:(NSNotification *)notification {
    NSLog(@"%@ %@ %@",notification,NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);
}

实际上,使用队列添加通知时,可以根据条件聚合缓存的通知,如果符合聚合条件,那么保留的是已缓存的那个通知,并且判断是否能够聚合的条件并不包括运行循环。这就意味着添加的通知在聚合时,会忽略运行循环的不同,这就需要我们自己考虑其中的影响了。

如下面的例子,在 MyRunloopMode 循环模式下,并没有接收到通知,因为在该循环模式添加的通知根据 NSNotificationCoalescingOnName 聚合条件,同 NSDefaultRunLoopMode 模式下的通知相同,所以通知并不会被添加到队列中。

当 5 秒后,运行循环重新开启,默认是 NSDefaultRunLoopMode 模式,第一个添加的通知便会发出。

- (void)btnClick {
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSNotificationQueue *queue = [NSNotificationQueue defaultQueue];
        NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);
        
        [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {

            NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);
        }];
        
        NSNotification *notification = [NSNotification notificationWithName:@"test" object:self];
        [queue enqueueNotification:notification postingStyle:NSPostASAP];
        
        notification = [NSNotification notificationWithName:@"test" object:nil];
        
        NSTimer *timer = [NSTimer timerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"%@ %@",NSThread.currentThread,NSRunLoop.currentRunLoop.currentMode);
            [queue enqueueNotification:notification
                          postingStyle:NSPostASAP
                          coalesceMask:NSNotificationCoalescingOnName
                              forModes:@[NSRunLoopCommonModes,@"MyRunloopMode"]];

        }];
        [NSRunLoop.currentRunLoop addTimer:timer forMode:@"MyRunloopMode"];
        
        NSDate *date = [NSDate dateWithTimeIntervalSinceNow:5];
        [NSRunLoop.currentRunLoop runMode:@"MyRunloopMode" beforeDate:date];
        
        [NSRunLoop.currentRunLoop run];
    });
}

自定义通知队列

使用下面的方法自定义通知队列,既然要自定义队列,那么大多也就意味着我们不想使用 NSNotificationCenter 类属性 defaultCenter 返回的默认的通知中心,而是自己指定推送通知队列中的通知的通知中心。

- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter;

实际上,defaultCenter 作为应用默认的通知中心,负责承接所有的系统的通知,也是我们应用中常用的通知中心。但是,这同样意味着接收每一个通知推送任务时,通知中心会去遍历注册表中的所有监听者,判断其是否需要执行相关任务。这对于通知繁多,性能敏感的应用是十分不利的。如果监听者要执行的任务十分耗时,那么便会阻塞当前线程,而该线程若恰好是主线程时,结果就相当不美好了。所以自定义自己的通知中心,分门别类的将通知发送给指定的通知中心去推送,可以提高性能和功能的模块化。

如果对通知的时效性要求较高,则可以配合自定义队列使用,从而过滤掉无效的通知。

每个线程都可以获取到同默认通知中心相关联的通知队列,即说明一个通知中心可以被多个通知队列相关联。但实际上,通知队列和通知中心和线程都是不想关的,即使在子线程中创建通知队列,在主线程中同样可以使用其添加通知。

这里所说的通知中心,是面向开发者的,同面向用户的通知中心是两个概念。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值