Think deeply about NSNotification

Recently, technology sharing, think of the topic of NSNotification, we may feel that the usual projects in the notice is very simple ah, is not what profound things, in fact, we went to see the official Apple API still presents some notice more complex usage, today to discuss with everybody NSNotification advanced.

Let’s take a look at Apple’s official documents:

In a multithreaded application, notifications are delivered the thread which notification was posted, which may not the same thread which in an observer registered itself. always be in in

That is to say:

In multithreaded applications, Notification in which the thread post, in which thread is forwarded, and not necessarily in the registered observer of the thread.

This is to say that the notification of the sending is synchronous, first of all, we first think about three questions to start our topic today.

Question 1: how to send notifications asynchronously
Question two: the relationship between notification and Runloop
Question three: how to specify a thread to send notifications

These three questions seem simple, if you do not have a thorough understanding of the notice, then your answer is obviously not the answer I want.

Question 1: how to send notifications asynchronously

Here we will use the NSNotificationqueue class, from the literal meaning we can understand the meaning of the notification queue.
NSNotificationQueue notification queue, used to manage multiple notification calls. Notification queues are usually maintained in the first in first out (FIFO) order. NSNotificationQueue is like a buffer pool that puts a notice into the pool and sends it to the observer in a specific way via NSNotificationCenter.

- (void notifuQueue) {/ / each process has a default notification queue, the default is not open, the bottom through a queue, the queue maintenance scheduling table NSNotification *notifi = [NSNotification notificationWithName:@ "Notification" object:nil]; NSNotificationQueue *queue = [NSNotificationQueue defaultQueue]; //FIFO NSLog (@ "Notifi before"); [queue enqueueNotification:notifi postingStyle:NSPostWhenIdle coalesceMask:NSNotificationCoalescingOnName forModes:[NSArray arrayWithObjects: NSDefaultRunLoopMode, nil]]; NSLog (@ "Notifi after"); NSPort *port = [[NSPort alloc] init]; [[NSRunLoop currentRunLoop] addPort:port forMode:NSRunLoopCommonModes]; [[NSRunLoop currentRunLoop] run]; NSLog (@ "runloop over");}
- (void) viewDidLoad {[super viewDidLoad]; [[NSNotificationCenter addObserver:self selector:@selector (handleNotifi:) name:@ "Notification" object:nil];}

First of all, we call the notifuQueue this method, send a notice, viewDidLoad registered observer, receive notification, you can see the print results are as follows:

Think deeply about NSNotification
print.Png

we can see the print results show that the notification is asynchronous execution.

Next explain the meaning of the following parameters:

Send mode

There are three types of NSPostingStyle:

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

NSPostWhenIdle: free send notification when the running loop is in a wait or idle state, send a notification, for the use of important notifications can be used.
NSPostASAP: as soon as the notification is sent to the current running loop iteration is completed, the notification will be sent, somewhat similar to the no delay timer.
NSPostNow: synchronous send notification if you do not use the same notification and postNotification: synchronization notification.

Merger notice

  • NSNotificationCoalescing also includes three types: typedef NS_OPTIONS (NSUInteger, NSNotificationCoalescing) {NSNotificationNoCoalescing = 0, NSNotificationCoalescingOnName = 1, NSNotificationCoalescingOnSender = 2}; NSNotificationNoCoalescing: without notice.
    NSNotificationCoalescingOnName: notification of the same name.
    NSNotificationCoalescingOnSender: merge notifications with the same notification and the same object.
  • By merging we can use to ensure that the same notification is sent only once.
  • ForModes: (nullable NSArray< NSRunLoopMode> *) modes can use different NSRunLoopMode to send notifications can be seen in mechanism of NSNotificationQueue and RunLoop and circulating a relationship, and the mechanism of association of RunLoop operation notice sent by NSNotificationQueue to the queue.

Question two: the relationship between notification and Runloop

- (void) notifiWithRunloop (observer = CFRunLoopObserverCreateWithHandler {CFRunLoopObserverRef (CFAllocatorGetDefault), kCFRunLoopAllActivities, YES, 0, (CFRunLoopObserverRef observer, CFRunLoopActivity ^ activity) {if (activity = = kCFRunLoopEntry) {NSLog ("enter @ Runloop");}else if (activity = = kCFRunLoopBeforeWaiting) {NSLog (@ "is about to enter the wait state"}else); if (activity = = kCFRunLoopAfterWaiting) {NSLog (@ "end wait state");}}); CFRunLoopAddObserver (CFRunLoopGetCurrent), observer (kCFRunLoopDefaultMode); CFRelease (observer); NSNotification *notification1 = [NSNotification notificationWithName:@ object:nil "notify" userInfo:@{@ "key": @ "1"}]; NSNotification *notification2 = [NSNotification notificationWithName:@ object:nil userInfo:@{@ "notify" "key" "2": @}]; NSNotification *notification3 = [NSNotification notificationWithName:@ object:nil "notify" userInfo:@{@ "key": @ 3 "[[NSNotificationQueue defaultQueue] enqueueNotification:notification1 postingStyle:NSPostWhenIdle}]; coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode]]; [[NSNotificationQueue defaultQueue] enqueueNotification:notification2 postingStyle:NSPostASAP coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode]] [[NSNotificationQueue defaultQueue] enqueueNotification:notification3 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing; forModes:@[NSDefaultRun LoopMode]]; NSPort *port = [[NSPort init]; [[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode] addPort:port; [[NSRunLoop currentRunLoop] run];}

First look at the print results, you can clearly see the conclusion:

NSConcreteNotification 0x618000051fa0 {name = notify; userInfo = {key = 3; 2017-03-30 17:27:09.626 Notification[59492:2559004] Runloop 2017-03-30}} into 17:27:09.627 Notification[59492:2559004] NSConcreteNotification 0x618000057910 {name = notify; userInfo = {key = 2;}} 2017-03-30 17:27:09.627 Notification[59492:2559004] 2017-03-30 17:27:09.627 is about to enter the wait state Notification[59492:2559004] NSConcreteNotification 0x618000051f10 {name = notify; userInfo = {key = 1; 2017-03-30 17:27: Notification[59492:2559004] 9.627}} the end of the 2017-03-30 17:27:09.628 Notification[59492:2559004] is about to enter the wait state wait state
  • We are very clear to see that the third notification did not enter the Runloop, because the above said that the NSPostNow is immediately executed, equivalent to the notification Center issued by the synchronization notice, so there is no relationship with Runloop.
  • Secondly, the notification of parameter NSPostASAP is faster than the NSPostWhenIdle notification to enter Runloop.

Question three: how to specify a thread to send notifications

First look at an example:

@implementation ViewController (void) - viewDidLoad (NSLog {[super viewDidLoad]; @ current thread, [NSThread currentThread] =% @ "); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (handleNotification:) name: TEST_NOTIFICATION object:nil]; dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION ^{object:nil userInfo:nil];});} - (void) handleNotification: (NSNotification * notification (@ NSLog) {current thread = [NSThread currentThread]% @, (@ NSLog);" test notification ");} @end

The results are as follows:

1, 2017-03-30 22:05:12.856 test[865:45102] current thread = < NSThread: 0x7fbb23412f30> {number = 1, name = 2, main} 2017-03-30 22:05:12.857 test[865:45174] current thread = < NSThread: 0x7fbb23552370> {number = 2, name = 3 (null), 2017-03-30 22:05:12.857 test[865:45174] test} notification
  • As we can see, although we have registered the notified observer in the main thread, the post of the Notification in the global queue is not processed in the main thread. So, this time it should be noted that if we want to deal with UI related operations in the callback, you need to ensure that the implementation of the callback in the main thread.
  • This time someone to say directly in the notification event where forced cut back to the main thread is not on the line, right, this is possible, but if I’m in the thread sending multiple notification, registration of multiple different observers, that if you want to notice in every processing places to cut the main thread, this is obviously not reasonable.
  • We first look at the official Apple document statement: For example, if an object running in a background thread is listening for notifications from the user interface, such as a window closing, you would like to receive the notifications in the background thread instead of the main thread. In these cases, you must capture the notifications as they are delivered on the default thread and redirect them to the appropriate thread.
  • Here is the “redirect”, that is, we are in the Notification default thread to capture the distribution of these notifications, and then redirect to the specified thread. I
  • A redirection of the implementation of the idea is to customize a notification queue (note, not a NSNotificationQueue object, but an array), so that the queue to maintain those we need to redirect Notification. We are still as usual to register a notification of the observer, when Notification came, first look at the post Notification thread is not what we expect the thread, if not, will the Notification store to our queue, and sends a signal (signal) desired threads. To tell this thread to deal with an Notification. After the specified thread receives the signal, the Notification is removed from the queue and processed.

Look at the official demo:

@interface ViewController (<); NSMachPortDelegate> @property (nonatomic) NSMutableArray *notifications; / / @property (nonatomic) NSThread notification queue *notificationThread; / / @property (nonatomic) NSLock expect thread * notificationLock; / / for the notification queue lock object lock, to avoid threading conflict @property (nonatomic) NSMachPort *notificationPort; / / to the communication port @end @implementation ViewController expects the thread sending signals - (void) viewDidLoad viewDidLoad] NSLog {[super (@ current; thread = [NSThread, currentThread]% @ "); / / self.notifications = [[NSMutableArray alloc] initialization init]; self.notificationLock = [[NSLock alloc] init]; self.notifi CationThread [NSThread = currentThread]; self.notificationPort = [[NSMachPort alloc] init]; self.notificationPort.delegate = self; / / to the thread run loop add port source / / when Mach message arrives at the receiving thread run and loop is not running, the kernel will save this message until the next loop [[NSRunLoop currentRunLoop] addPort:self.notificationPort forMode: into run (__bridge * NSString) kCFRunLoopCommonModes]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (processNotification:) name:@ "TestNotification" object:nil]; dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), [[NSNotificationCenter defaultCenter] postNoti ^{ FicationName:TEST_NOTIFICATION object:nil userInfo:nil];});} - (void) handleMachMessage: (void * MSG) {[self.notificationLock lock]; while ([self.notifications count]) {NSNotification *notification = [self.notifications objectAtIndex: 0]; [self.notifications removeObjectAtIndex:0]; [self.notificationLock unlock]; [self processNotification:notification]; [self.notificationLock lock]; [self.notificationLock unlock];};} - (void) processNotification: (NSNotification * notification) {if ([NSThread currentThread]! = _notificationThread) {/ / Forward the notification to the correct thread. [self.notificationLock lock]; [self.notifications addObject:noti Fication]; [self.notificationLock unlock]; [self.notificationPort sendBeforeDate:[NSDate date] components:nil from:nil reserved:0];} else {/ / Process the notification here; NSLog (@ "current thread = [NSThread currentThread]% @, (@ NSLog);" process notification ");}} @end

The results are as follows:

1, 2017-03-30 23:38:31.637 test[1474:92483] current thread = < NSThread: 0x7ffa4070ed50> {number = 1, name = 2, main} 2017-03-30 23:38:31.663 test[1474:92483] current thread = < NSThread: 0x7ffa4070ed50> {number = 1, name = 3, main} 2017-03-30 23:38:31.663 test[1474:92483] process notification

It can be clearly seen that a notification has been sent in an asynchronous thread that is captured in the main thread.
but the implementation of Apple also proposed his limitations:

This implementation is limited in several aspects. First, all threaded notifications processed by this object must pass through the same method (processNotification:). Second, each object must provide its own implementation and communication port. A better, but more complex, implementation would generalize the behavior into either a subclass of NSNotificationCenter or a separate class that would have one notification queue for each thread and be able to deliver notifications to multiple observer objects and methods.

Solution

GitHub has put forward a kind of solution, it is to write a Notification class management link https://github.com/zachwangb/GYNotificationCenter
posted here, want to achieve two:

  • Easy to control thread switching
  • To facilitate the remove observer

Specific code is of interest to students can read their own, here do not do a detailed analysis of the.

Write so much, if you like, please point a praise, thank you!