This article is the fourth part of a series about
NSNotificationCenter. Head over part 1 for the basics of receiving and sending notifications.
Notifications are synchronous by default, meaning that the object that posts a notification must wait until the notification center dispatches the notification to all observers and each of them handles it before it can resume its execution. This might be inconvenient for objects that post frequent notifications (e.g., a UI component that notifies on every touch movement), and Foundation offers mechanisms to coalesce and delay notification via
NSNotificationQueue is a buffer of notifications that handles when to post them based on given criteria and the run loop, in a FIFO fashion. Each thread has a default notification queue via
A word of warning
NSNotificationQueue documentation appears to be in a state of flux after a OS X release note that basically discourages its use. From the release note:
NSNotificationQueue is an API and mechanism related to run loops (NSRunLoop), and the running of run loops. In particular, posting of notifications via the notification queue is driven by the running of its associated run loop. However, there is no definition for what run loop a notification queue uses, and there is no way for a client to configure that. In fact, any given notification queue might be “tickled” into posting by nearly any run loop and different ones at different times (and given that enqueued notifications are also mode-specific, what mode(s) the run loop(s) are running in any any given time also come into play). Further, although each notification queue is “thread-specific” (see the +defaultQueue documentation), there has never been any relationship between that thread and the run loop used by a notification queue. This is all more and more problematic as more and more things move to happening on different and new threads.
The practical upshot is:
- Notifications posted through a queue might not be posted in any particular “timely” fashion;
- Notifications posted through a queue might not ever be posted (the run loop of the thread that the queue chose to ask to poke it might not be run again; for example, the thread of that run loop might exit and the notification queue discarded);
- Notifications can be posted by any given queue on a different thread than the thread the queue is the default queue for (when a queue is a default queue for some thread);
- Notifications can be posted by any given queue on different threads over time;
- There is no necessary/guaranteed relationship between the thread(s) on which notifications are enqueued and those on which the notifications eventually get posted (delivered) for any notification queue
This is true for all releases of OS X. Foundation and AppKit do not use NSNotificationQueue themselves, partly for these reasons.
Taking this into account, and until Apple clarifies their stance on this class, you shouldn’t assume that notifications posted with
NSNotificationQueue will ever be posted, or received in any particular thread.
Delaying and Coalescing notifications
enqueueNotification:postingStyle: is used to post a notification asynchronously. Unlike
postNotification: and its convenience methods, it immediately returns to the invoking object after putting the notification in the queue. The delay will depend of the given posting style. There are three options:
NSPostNow: The notification will be posted synchronously. If not used with coalescing, this would be exactly the same as
NSPostASAP: The notification will be posted when the current iteration of the run loop completes, much like a zero-delay timer (credit to Mike Ask for the analogy).
NSPostWhenIdle: The notification will be posted when the run loop is in a wait state or idle. This is handy for notifications that are not very important.
NSString *notificationName; id sender; NSNotification *notification = [NSNotification notificationWithName:notificationName object:sender]; [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];
Delayed notifications are most useful when combined with coalescing, as the object creating the notifications might queue more before the first one is posted. By default, notifications queued with
enqueueNotification:postingStyle: are coalesced by both the notification name and sender. The result is that when a notification is posted, it is the first notification queued for the corresponding name and sender; the rest have been coalesced (discarded, in effect).
By coalescing notifications we can make sure that a single notification is posted for repeatable events (e.g., receiving fresh data from a socket). This can be further controlled with
enqueueNotification:postingStyle:coalesceMask:forModes:, which expands the delay options covered above with a coalescing criteria. The options are:
NSNotificationNoCoalescing: Do not coalesce notifications in the queue.
NSNotificationCoalescingOnName: Coalesce notifications with the same name.
NSNotificationCoalescingOnSender: Coalesce notifications with the same object.
The coalesce mask is a bit mask that allows us to combine
NSNotificationCoalescingOnSender to obtain the default behavior of
Additionally, we can provide an array of run modes in which we want the notification to be posted. This typically will be
nil, which is the equivalent to
NSDefaultRunLoopMode. The following lines provide the same result:
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP]; [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender forModes:nil]; [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender forModes:@[NSDefaultRunLoopMode]];
Given that it is not possible to know exactly when the notification will be posted, the sender might be deallocated before its notifications are posted. A way around this is to dequeue the sender’s notifications before
- Part 1: Receiving and testing notifications
- Part 2: Implementing the observer pattern with notifications
- Part 3: Unit testing notifications with OCMock
- Part 4: Asynchronous notifications with NSNotificationQueue
- Annex A: List of iOS public notifications
- Annex B: List of OS X public notifications