JC专题summer的ios小记iOS开发技巧

你可能不知道的Notification

2016-10-08  本文已影响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

上一篇 下一篇

猜你喜欢

热点阅读