你可能不知道的Notification

作者: 微微笑的蜗牛 | 来源:发表于2016-10-08 19:10 被阅读1097次

    Notification,项目中使用还是蛮多的,post发送通知,addObserver监听接收通知,听起来很简单,对吧。但是还是会有些大家可能会忽视的地方。

    同步or异步

    [NotificationCenter defaultCenter] postNotification],这种方式是同步的,并且在哪个线程发,就在哪个线程收。

    同步的意思,就是消息接收者全部处理完消息之后,post这方才会继续往下执行,因此,尽量不要做太耗时的操作。

    由于消息收和发都在同一个线程中。所以,尽量在主线程中post,不然会引起不必要的麻烦,ui刷新问题,崩溃问题等等。

    addObserver调用多次

    addObserver如果添加多次,当post的时候,也会收到多次。类似这种:

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

    observer的移除

    这个是老生常谈了,一定记得要移除,否则崩溃很容易发生。不过在iOS 9以后,不需要手动移除。

    If your app targets iOS 9.0 and later or OS X v10.11 and later, you don't need to unregister an observer in its deallocation method。

    异步通知

    异步的好处,不必等待所有的消息处理者处理完成,可以立马返回。

    如果要发送异步通知,可以使用NSNotificationQueue,将通知enqueueNotification之后,会在合适的时候将通知发送给NotificationCenter,NotificationCenter会真正的将消息post出去。

    [[NSNotificationQueue defaultQueue] enqueueNotification:[NSNotification notificationWithName:@"task" object:self] postingStyle:NSPostWhenIdle];
    

    可以指定runloop mode,当runloop处理该种mode的时候,才会发送通知。

    也可以指定发送通知的时机,有如下3种。

    typedef NS_ENUM(NSUInteger, NSPostingStyle) {
        NSPostWhenIdle = 1,
        NSPostASAP = 2,
        NSPostNow = 3
    };
    

    NSPostWhenIdle,在runloop空闲时发送,当runloop要退出时,不会发送。

    NSPostASAP:Posting As Soon As Possible,在runloop的当前迭代完成时发送给通知中心,但是当前mode和设定的mode要一致。

    NSPostNow:就是同步调用。

    聚合发送

    当在一段时间内,enqueue了多个通知,系统不会每个都发送,如果在队列中已有该种通知,则不会进入队列,只保留第一个通知。有如下3中可选方式。

    typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
        NSNotificationNoCoalescing = 0,         // 不聚合
        NSNotificationCoalescingOnName = 1,         // 根据通知名聚合
        NSNotificationCoalescingOnSender = 2       // 根据发送者聚合
    };
    

    NSNotificationCoalescingOnName:根据通知名称来聚合,如果在一段时间内,
    NotificationName="UpdateMyProfileNotification"有多个,则将他们聚合起来,只发送一个。

    NSNotificationCoalescingOnSender:根据发送方来聚合。

    我做了下测试,的确只收到一次通知。并且是第一个通知。

    for (int i = 0; i < 2; i++) {
            NSNotification *notification = [NSNotification notificationWithName:@"TestNotification" object:@(i)];
            [[NSNotificationQueue defaultQueue] enqueueNotification:notification                     
            postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil];
        }
    

    在指定线程接收通知

    上面说到,收发都在一个线程中,如果想要做到,在某个指定的线程接收通知,该如何做呢?苹果文档上提及了实现思路。

    先简单介绍下mach port,它主要用来线程间通信。简单来说,就是接收线程中注册NSMachPort,在另外的线程中使用此port发送消息,则注册线程会收到相应消息,调用handleMachMessage来处理。

    主要思路:

    1. 定义一个中间对象NotificationHandler,用来专门接收通知,包括一个队列,接收通知的线程,mach port,lock。

    2. 首先NotificationHandler会注册一个通知,对应的处理函数为processNotification,当在其他线程中post时,processNotification会被调用,进行如下处理。如果收到的通知跟指定的线程一样,则处理消息,反之,则添加到队列,同时通过port发送消息给指定线程。注意多线程中,对队列的处理,要加锁

    3. 指定线程收到回调handleMachMessage,首先会将通知删除,然后调用processNotification进行处理,继续以上过程。

    
    - (instancetype)init {
        if (self = [super init]) {
            [self setUpThreadingSupport];
    
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"notification" object:nil];
        }
    
        return self;
    }
    
    - (void)dealloc {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    
    - (void)setUpThreadingSupport
    {
        if (self.notifications) {
            return;
        }
        self.notifications = [NSMutableArray new];
        self.lock = [NSLock new];
        self.thread = [NSThread currentThread];
    
        self.port = [NSMachPort new];
        [self.port setDelegate:self];
        
        [[NSRunLoop currentRunLoop] addPort:self.port forMode:(__bridge NSString *) kCFRunLoopCommonModes];
    }
    
    - (void)handleMachMessage:(void *)msg
    {
        [self.lock lock];
    
        // 由于大量port message在同时被发送时,可能会被丢弃,为了防止没有处理到,这里遍历数组来进行处理。
        while ([self.notifications count]) {
            NSNotification *notification = [self.notifications objectAtIndex:0];
            [self.notifications removeObjectAtIndex:0];
            [self.lock unlock];
            [self processNotification:notification];
            [self.lock lock];
        }
    
        [self.lock unlock];
    }
    
    - (void)processNotification:(NSNotification *)notification
    {
        NSThread *ct = [NSThread currentThread];
        // 不是指定线程
        if (ct != _thread) {
            [self.lock lock];
            // 添加到队列
            [self.notifications addObject:notification];
            [self.lock unlock];
    
            // 通过mach port发送消息
            [self.port sendBeforeDate:[NSDate date] components:nil from:nil reserved:0];
        }else{
            NSLog(@"process notification %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
        }
    }
    
    

    如果,我们想要在主线程中接收通知,在viewDidLoad中。

    - (void)viewDidLoad {
          self.notificationHandler = [NotificationHandler new];
    
    // 在子线程中post,会在主线程中收到
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"post notification %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
            [[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];
    
            [[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];
        });
    }
    

    或者,需要在某个子线程中接收,可以这样。

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(startThread) object:nil];
        [self.thread start];
    }
    
    - (void)startThread {
    
        NSLog(@"startThread %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
    
        self.notificationHandler = [NotificationHandler new];
        
        // 在另外的子线程中发送通知dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"post notification %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]);
            [[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];
    
            [[NSNotificationCenter defaultCenter] postNotificationName:@"notification" object:nil];
        });
    
        // 线程中需要自己启动runloop
        [[NSRunLoop currentRunLoop] run];
    }
    
    

    参考:
    Notifications

    相关文章

      网友评论

      本文标题:你可能不知道的Notification

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