mikeash.com: just this guy, you know?

Posted at 2010-01-08 07:21 | RSS feed (Full text feed) | Blog Index
Next article: Friday Q&A 2010-01-15: Stack and Heap Objects in Objective-C
Previous article: Friday Q&A 2010-01-01: NSRunLoop Internals
Tags: cocoa fridayqna notifications
Friday Q&A 2010-01-08: NSNotificationQueue
by Mike Ash  

It's that time of the week again. No, it's not just time to go get drunk, but time for Friday Q&A! This week's topic, suggested by Christopher Lloyd of Cocotron (a really neat open source project that lets you write Objective-C/Cocoa code for non-Mac platforms like Windows), is NSNotificationQueue, a little-known, poorly-understood, but handy Foundation class.

NSNotificationQueue works in close concert with NSRunLoop. If you haven't already, it might be a good idea to read my post from last week on NSRunLoop internals.

(No) Threading
Many people, upon confronted with NSNotificationQueue, immediately think that it allows posting notifications across threads. (Well, I did, anyway.) In fact, NSNotificationQueue is completely unrelated to threads! Like NSRunLoop, there exists a single NSNotificationQueue per thread, and that per-thread instance can't be used from other threasd.

So What Is It?
Simply put, the primary mission of NSNotificationQueue is to delay posting of a notification, and to allow coalescing of notifications.

Delayed Notifications
Sometimes you don't want to post a notification immediately. This is especially true if you want to coalesce multiple identical notifications; you can't do that unless you delay posting the first one. It can be useful for other scenarios as well, such as wanting to give calling code a chance to run before the notification's observers run. (In this respect, it's very similar to passing zero delay to performSelector:withObject:afterDelay: in order to have some code run at the next runloop cycle.)

NSNotificationQueue provides three posting styles for notifications:

    NSPostWhenIdle = 1,
    NSPostASAP = 2,
    NSPostNow = 3
Let's take them bottom to top.

NSPostNow is the easiest to understand. This has the same semantics as posting the notification directly to the NSNotificationCenter, in that the notification is posted immediately and observers are notified before control returns to the caller. The only reason to use this instead of NSNotificationCenter is because it can be used to coalesce previously enqueued notifications before posting.

NSPostASAP is much like a zero-delay timer. The notification is not posted immediately, but will be posted as soon as control returns to the runloop. The usage scenarios are much like for a zero-delay timer.

NSPostWhenIdle will wait until the runloop is idle, then post the notification. You can think of this as using a zero-delay timer with low priority. As long as the runloop has other work to do, the notification will not be posted. Once the runloop runs out of stuff to do, the notification will be posted. This is useful if you want to wait until your program has not only finished the currently executing code, but has nothing else to do. For example, imagine tracking mouse movement and performing an expensive update on the basis of that movement. Performing that update with every movement will make the program unresponsive, but you still want to update as often as possible within reason. Using NSPostWhenIdle will accomplish this. As long as more mouse movement events are pending, the notification will not be posted. If the user pauses for a moment, the runloop will clear out and the notification will be posted. If the user has a really fast computer, or the computation takes less time than expected, the runloop may have time to idle in between mouse moved events, and your update will then happen more frequently.

The real power of NSNotificationQueue is in coalescing. What does that mean?

When posting with NSPostASAP or NSPostWhenIdle, the notification is not posted immediately, but rather is queued. Coalescing means that if a notification is posted which matches one already in the queue, the two are merged, so that only a single notification is posted to observers.

This behavior can be really handy. Imagine a loop modifying a bunch of objects which posts notifications that cause other parts of the application to update their idea of the world. If they only need one notification at the end of all the modifications, coalescing will allow that other code to avoid a lot of needless computation that would occur if every modification caused a separate notification.

NSNotificationQueue provides three types of coalescing.

These are bitwise flags, so you can combine them. Writing NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender means that coalescing is performed if two notifications share both the same name and the same sender object. (Most of the time when coalescing, this is what you want, and it's what -enqueueNotification:postingStyle: implicitly uses.)

With coalescing, the semantics of NSPostNow make more sense. By using NSPostNow with NSNotificationQueue, any matching enqueued notifications will be coalesced with the one being posted before it's posted, essentially clearing them out and posting the coalesced notification earlier than would have otherwise happened.

These coalescing flags can also be used to remove notifications from the queue without posting them, using the -dequeueNotificationsMatching:coalesceMask: method.

NSNotificationQueue is pretty straightforward to use. Rather than standard code using NSNotificationCenter, you more or less drop in NSNotificationQueue. As a concrete example, imagine you have an NSSlider set to be "continuous", so that it sends its action message every time the mouse is moved, even while it's down:

    - (IBAction)sliderMoved: (id)sender
        [self updateWithNewSliderValue: [sender doubleValue]];
But you also have some updates that you only want to run once the mouse has been released. Since the runloop is run in NSEventTrackingRunLoopMode while the mouse is down, you can just post a notification using NSPostASAP onto the NSNotificationQueue, and the notification will be posted only once the mouse is released. By coalescing, this code ensures that only one notification is posted when the mouse is released, even though this action message may be called many times:
        NSNotification *note = [NSNotification notificationWithName: SliderDoneMovingNotification object: self];
        [[NSNotificationQueue defaultQueue] enqueueNotification: note postingStyle: NSPostASAP];
Imagine that you also want to update a value while the slider is moving, but that this update is expensive to perform, so you want to keep the application responsive. Using NSPostWhenIdle and NSEventTrackingRunLoopMode will allow this:
        note = [NSNotification notificationWithName: ExpensiveSliderUpdate object: self];
        NSArray *modes = [NSArray arrayWithObject: NSEventTrackingRunLoopMode];
        [[NSNotificationQueue defaultQueue] enqueueNotification: note
                                                   postingStyle: NSPostWhenIdle
                                                   coalesceMask: NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender
                                                   forModes: modes];
To avoid a pending notification waiting around a long time if the mouse is immediately released after this code executes, you'll want to observe SliderDoneMovingNotification and remove ExpensiveSliderUpdate from the queue:
    - (void)sliderDoneMoving: (NSNotification *)note
        NSNotification *note = [NSNotification notificationWithName: ExpensiveSliderUpdate object: self];
        [[NSNotificationQueue defaultQueue] dequeueNotificationsMatching: note
                                                            coalesceMask: NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender];
NSNotificationQueue may not be well known in general, but now you know how it works, what it's good for, and have some ideas for how to put it to work.

That's it for this week. Come back next week for another exciting edition. (A warning: I'm going to be making a long trip not long before next Friday, and so there's some possibility that I'll miss next week's edition. In that event, my instructions to you, the reader, are to panic as thoroughly as possible until the following Friday.)

As always, Friday Q&A is driven by reader suggestions. If you have an idea for a topic to cover here, please send it in!

Did you enjoy this article? I'm selling a whole book full of them. It's available for iBooks and Kindle, plus a direct download in PDF and ePub format. It's also available in paper for the old-fashioned. Click here for more information.


Jonathan Wight at 2010-01-08 14:18:35:
Crap. I've written code that basically does everything that NSNotificationQueue is doing. I should have just used NSNotificationQueue - now I know to use it in future.

It's definitely one of those classes that you look at in the docs and think "that's useful, wonder what I should use it for", and then you promptly forget about it.

More topics like this please! (And the NSRunloop post was great too). I'll scan through Foundation.framework and look for other obscure but useful classes.

mikeash at 2010-01-08 15:10:50:
I wondered many times while writing this post whether other people would find it interesting, or if I was the only one who missed the significance of this class for so long. Looks like I was right to go ahead!

If you can find any more weird, obscure, useful classes to expound upon, let me know!

Daniel Hedrick at 2010-01-08 15:27:31:
Quick question about these two methods you're defining (mostly about semantics, I guess)...

Would these be declared in the controller that is the target of the slider?

I'm assuming so, but I'm a little rusty on my Cocoa and not in front of a Mac to test (for shame!).

Dan Leehr at 2010-01-08 15:38:55:
Great post, an under-covered subject that had my gears turning the whole time. I use notifications very heavily in my iPhone app and I can think of several places where I should be using notification queues to coalesce them.
Also, this solidifies the run loop concepts even more, so bonus!

Sarah Holbrook at 2010-01-08 15:45:08:
This is really helpful when running on the main event loop.

For any thread that has a finite life, be careful when using anything but NSPostNow. The delayed notifications are not guaranteed to be sent. I don't know of an elegant solution; one workaround is to give the current runloop an extra workout before the thread dies. In practice I've seen delayed notifications on a background thread lead to many bugs when not managed carefully.

It depends on what you're trying to acheive but NSOperation dependencies offer an alternate for "do this afterwards" when you're off the event loop (but need to be configured before the initial work begins).

mikeash at 2010-01-08 15:58:54:
Daniel Hedrick: Yes, they would be in the slider's target. sliderMoved: would be set as the target of the slider. You'd also need some code somewhere (in the init method, probably) to add the object as an observer for SliderDoneMovingNotification with sliderDoneMoving: as the observation method. There's no automatic hookup for that sort of thing, unfortunately.

Sarah Holbrook: The problem of secondary threads with finite lifetimes is an interesting one. I can see a few reasonable solutions:

1) Only use NSNotificationQueue for things that can withstand not being delivered.

2) Ship the queued notifications to the main thread, or to another thread which lives forever.

3) When exiting the thread, add another notification with NSPostWhenIdle, then spin the runloop until it fires. This should mean all other pending notifications have fired as well... assuming none of the observers use NSNotificationQueue!

Definitely takes some added thought and work for this case, though.

Speaking of NSOperation, using NSNotificationQueue from an NSOperationQueue or a GCD queue could be a really good way to run into trouble.

Steven Degutis at 2010-01-08 17:50:15:
Interesting you mention this class, I only noticed it for the first time a month ago when I was redoing The Gist (OSS Twitter app) to no longer use bindings to update other observers that new tweets were imported into the app from Twitter. This class makes perfect sense in this context because tweets can be imported individually or in large, indeterminately-sized batches.

Sarah Holbrook at 2010-01-08 18:26:17:
Mike, very valid points. Posting to the main thread (#2 in your followup) is really helpful especially when the intent is for the UI/controller classes to act on the notification. It's another one to be careful with though; I've seen it lead to performance problems that aren't obvious when sampling.

Adding one more notification (#3) seems like it could work. I'm curious if anyone's tested it. If there were another "idle" notification is it guaranteed to send first? I haven't verified if the queue is mostly in name or whether it's truly FIFO with delayed notifications.

mikeash at 2010-01-09 03:59:16:
Yes, I've found -performSelectorOnMainThread: to be troublesome when used in a tight loop. It operates at a high priority and can cause the app to become unresponsive if you aren't careful.

For #3, from the docs:

A notification queue maintains notifications (instances of NSNotification) generally in a first in first out (FIFO) order.

That word "generally" is a bit disconcerting, but I think that refers to the fact that e.g. NSPostASAP notifications will happen before NSPostWhenIdle even if the NSPostASAP happened later.

I have not tested it, though, so it could be way wrong.

John McLaughlin at 2010-01-09 06:12:47:
Great Post -- Many a time I've wondered how to execute something lazily (and I just don't mean my personal life!)

I sort of wish they hadn't split NSNotification and NSNotificationQueue but instead provided a single interface and allow flags or calling semantics.

I'm sure I'll use soon!

Frédéric Testuz at 2010-01-09 08:01:57:
I used NSNotificationQueue some years ago. I had never used thread, had to process a large amount of data and wanted to keep my app responsive. But at this time, I didn't have the time to learn about threading issues.

So I break my code in small portions. At the end of a portion I sent a notification with NSPostWhenIdle. When the notification was post I proceed with the next portion. It worked very well.

mikeash at 2010-01-10 06:10:33:
It would be pretty trivial to add a category to NSNotificationCenter to add coalescing/delayed methods that just call through to NSNotificationQueue, if you wanted to. Of course, if your grip is that NSNotificationQueue is hard to discover, that doesn't help at all.

Colin Barrett at 2010-01-10 21:50:16:
Cocoa's text system is complicated, powerful and relatively poorly understood. Sounds like a great topic for Friday Q&A to me.

mikeash at 2010-01-12 06:21:45:
It would be a great topic if it weren't for the fact that I don't know it well, and its great complexity doesn't translate to being all that interesting in terms of how it works. (Obviously it's interesting in terms of producing extremely useful results.) Thanks for the suggestion, though, I'll make a note of it in case I change my mind later.

Jeremy W. Sherman at 2010-02-13 19:25:21:
You might want to be careful of NSNotificationQueue. See the "Using NSNotificationQueue Warning" section in the Foundation release notes <URL:http://developer.apple.com/mac/library/releasenotes/Cocoa/Foundation.html&;gt;. I've taken the liberty of quoting the passage in its entirety:

Using NSNotificationQueue Warning (New since WWDC 2009)
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 Mac OS X. Foundation and AppKit do not use NSNotificationQueue themselves, partly for these reasons.

mikeash at 2010-02-13 22:16:38:
Well that's some Grade A BS right there. The described behavior makes the class completely useless, and therefore can only be considered a bug, even though it may align with an extremely legalistic and pathological reading of the documentation in question. Apple should have either deprecated the class or fixed it. Hiding the problem away in a release note is completely ridiculous.

Jeremy W. Sherman at 2010-02-14 19:53:01:
"Apple should have either deprecated the class or fixed it. Hiding the problem away in a release note is completely ridiculous."

Far too much useful information is hidden away in release notes and never makes it into the permanent documentation. At the very least, they could have updated the NSNotificationQueue class reference documentation to include the warning.

The practical upshot of this is that the release notes are essential reading if you want to learn the fine and ugly details of the frameworks as they stand, rather than as they were documented four or five years ago. This, unfortunately, significantly reduces the utility of the documentation that you can readily access from Xcode: essential information is no longer just an Option-double-click away.

mikeash at 2010-02-14 21:51:15:
Well, I don't think updating the class documentation would be enough here, although it certainly would have been better. If those warnings are correct then the class simply cannot be used safely. If that's the case, and they don't fix it, then they need to officially deprecate it to indicate that it's bad and you shouldn't touch it.

Hoang Toan at 2012-04-11 04:52:59:
Great post ! It's useful for me. Thanks so much.

folex at 2014-03-11 14:39:27:
Are problems in the warning still persist?

Hano at 2014-08-01 17:29:08:
I'm also a bit curious if these warning still persists. How about iOS?

Comments RSS feed for this page

Add your thoughts, post a comment:

Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.

Web site:
Formatting: <i> <b> <blockquote> <code>. URLs are automatically hyperlinked.
Code syntax highlighting thanks to Pygments.
Hosted at DigitalOcean.