Notifications, part 2 - Handling and Spying

Last time you saw how to register for notifications. Now time to handle them!

With the classic notification registration API, you specify an object to be sent a message, and the selector to use. That message can take at most one argument, which is a pointer to an NSNotification object.

When you register for notifications using a block, you supply a queue, and a block to schedule on that queue when the notification is posted. Because the code is in the block there’s no need to implement a method, or even have an object involved. Of course, if a lot of work is being done in the handler, you’ll want to factor it out into its own method or function.

For completeness, here’s an example from last time. The notification block takes an NSNotification object:

Recall that the token is an opaque object that you can use to unregister the notification later.

The Notification Object

So, about that notification object. That’s how the code posting the notification “Hey! The network just went down” communicates with the code handling the notification. “Oh dear, I need to redraw my view.”

There’s three pieces of information baked into the notification object:

The notification name is an NSString that’s used to broadcast the notification, in the code blocks above, the name would be kNetworkWentByeByeNotification. Sometimes this might be all the information you need to convey. “This thing happened.”

You can also use the notification name to disambiguate multiple notifications that call the same method. There might be a large body of common code that runs for each notification (perhaps for network-connected and also for network-disconnected), but then a little bit of extra code for just one of the cases (update a timestamp label when the network goes away):

Say you had a view class that broadcast when it was tapped. “Yo! I was tapped!”. And you put two of them on screen. One implementation approach would be to have two different methods (or blocks) that handle the notifications. (This is actually the approach I’d take). You could also have a single notification handler that discriminated on the object:

The third piece of information is the userInfo, a dictionary containing arbitrary key/value pairs that the notification poster decided would be of interest to anyone receiving the notification. There are no predefined conventions about what gets passed in the userInfo, so you can do whatever you want. For the network monitor, it could include such information like the exact network interface that went down, the time it went down, the IP address and port it was using, the time since the last connection, and the Bonjour name used to advertise a service.

The advantage of this method is for programmers that aren’t very familiar with your API. They can figure out which dictionary keys to access to get at the data they want. This is the same userInfo dict, but with the other nomenclature:

It’s obvious that if you want the network interface, you would use kNetworkInterfaceKey to get at it.

Spying

It’s pretty easy to spy on notifications flying around your program. The notification key names are usually self-documenting (especially those coming from cocoa), as are the contents of the userInfo.

Recently I had a need to see notifications flying around my app. Specifically, I was wondering why I wasn’t getting NSMetadataQuery notifications for my iCloud document container. I thought I had everything set up, and checked my cloud document marklar with NSFileManager and there were files there, but nothing came back with my metadata query. I know there are some metadata query notifications happening, so I wanted to look at them.

It’s pretty easy to spy on all the notifications that are happening. Just register a notification handler with a nil object and a nil name. Passing nil for those tell the notification center to treat them like wild cards. Here’s a way to spy on everything going through your app’s notification center:

This ended up being useful to me because I saw no metadata notifications. Then I noticed I never called -startQuery. I make dumb errors too.

Distributed Notifications

We usually use the default notification center. You’re welcome to create your own notification centers if you wish (therefore NSNotificationCenter is not a singleton class). On the desktop, you can access NSDistributedNotificationCenter, which is a easy form of interprocess communication. One process can post a distributed notification, and other processes can register handlers to receive those notifications. To see everything that comes across distributed notifications, you just add an observer with nil name and object like with the default notification center:

Workspace Notifications

Finally, there’s NSWorkspace, which has its own notification center. NSWorkspace will tip you off to cool events like application launches and exits, applications juggling in and out, and the machine going to sleep. Registering a notification is the same as before, just using a different notification center:

You can see I juggled from Scrivener (where I’m writing this posting) to the Terminal (where I’m working on the code and running it), the system going to sleep and waking up, and then the backup helper exiting.

If you’re having trouble seeing workspace notifications, you might need to do the hack I outlined earlier. You can get the code to spy on notifications from a command-line tool at this gist, and you’re welcome to paste it into your own applications to spy on the notifications flying around.