Tag Archives: NSNotificationQueue

NSNotificationCenter part 4: Asynchronous notifications with NSNotificationQueue

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.

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 defaultQueue.

A word of warning

The 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

NSNotificationQueue‘s enqueueNotification:postingStyle: is used to post a notification asynchronously. Unlike NSNotificationCenter‘s 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 postNotification:.
  • 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.

An example:

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 NSNotificationQueue‘s 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 NSNotificationCoalescingOnName and NSNotificationCoalescingOnSender to obtain the default behavior of enqueueNotification:postingStyle:.

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:

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 dealloc with dequeueNotificationsMatching:coalesceMask:.

Further reading