Notification, which is used a lot in projects, post sends notifications, addObserver listens for notifications, sounds pretty simple, right? But there are some things that you might miss.

Synchronous or asynchronous

[NotificationCenter defaultCenter] postNotification [NotificationCenter defaultCenter] postNotification [NotificationCenter defaultCenter] postNotification [NotificationCenter defaultCenter] postNotification [NotificationCenter defaultCenter] postNotification

Synchronization means that the post will continue after the receiver has processed the message, so try not to do too time-consuming operations.

Because messages are received and sent in the same thread. Therefore, try to post in the main thread, otherwise it will cause unnecessary trouble, UI refresh problems, crash problems, etc.

AddObserver is called multiple times

If addObserver is added multiple times, it will be received multiple times when it is posted. Something like this:

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

The removal of the observer

This is a cliche, always remember to remove it, otherwise crashes can easily occur. But after iOS 9, you don’t need to manually remove it.

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.

Asynchronous notification

The advantage of asynchrony is that you don’t have to wait for all the message handlers to finish processing and can return immediately.

If you want to send asynchronous notifications, you can use an NSNotificationQueue, which will send notifications to the NotificationCenter when appropriate after enqueueNotification is notified, NotificationCenter actually posts the message.

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

You can specify a runloop mode, and only when runloop handles this mode will notifications be sent.

You can also specify the notification time.

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

NSPostWhenIdle, sent when runloop is idle, not sent when runloop is about to exit.

NSPostASAP: Posting As Soon As Possible, sent to the notification center when the current iteration of runloop is complete, but the current mode is the same As the set mode.

NSPostNow: synchronous call.

The aggregation send

If the enQueue has multiple notifications in a period of time, the system does not send each one. If the enqueue already has this notification, only the first one is kept. The options are as follows: 3.

typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) { NSNotificationNoCoalescing = 0, / / no aggregation NSNotificationCoalescingOnName = 1, / / according to the notice of polymerization NSNotificationCoalescingOnSender = 2 / / according to sender polymerization};Copy the code

Name of NSNotificationCoalescingOnName: according to the notice to aggregate, if over a period of time, NotificationName = “UpdateMyProfileNotification” have more than one, will they together, send only one.

NSNotificationCoalescingOnSender: aggregate according to the sender.

I did run some tests, and I only got one notification. And that’s the first notice.

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

Receives notifications on the specified thread

If you want to receive notifications on a specified thread, how do you do that? The apple documentation describes how to do this.

A brief introduction to Mach Port, which is primarily used for interthread communication. In simple terms, the receiving thread registers the NSMachPort. If another thread uses this port to send messages, the registered thread receives the corresponding message and calls handleMachMessage to process it.

Main ideas:

  1. Define an intermediate object NotificationHandler for receiving notifications, including a queue, a thread to receive notifications, a Mach port, and a lock.

  2. First a NotificationHandler registers a notification. The corresponding handler is processNotification, which is called when a post is posted in another thread and does the following. If the notification is received as the specified thread, the message is processed; otherwise, the message is added to the queue and sent to the specified thread via port. Notice that in multithreading, you have to lock the queue.

  3. The specified thread receives the handleMachMessage callback, first removes the notification, then calls processNotification to process it and continues the process.

- (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]; // If a large number of port messages are sent at the same time, they may be discarded. 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]; // Add to queue [self.notifications addObject:notification]; [self.lock unlock]; // Send messages via the Mach port [self.port sendBeforeDate:[NSDate date] Components :nil from:nil reserved:0]; }else{ NSLog(@"process notification %@,is main %zd",[NSThread currentThread],[NSThread isMainThread]); }}Copy the code

So, if we want to get notifications in the main thread, in viewDidLoad.

- (void)viewDidLoad { self.notificationHandler = [NotificationHandler new]; // Post in the child thread, 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]; }); }Copy the code

Or if you need to receive it in a child thread, you can do that.

- (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]; }Copy the code

Reference: the Notifications