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.
So, given this registration:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver: self
selector: @selector(networkByeBye:)
name: kNetworkWentByeByeNotification
object: self.networkMonitor];
The resulting handler method would look like this:
- (void) networkByeBye: (NSNotification *) notification {
// handle the notification
} // networkByeBye
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:
_token = [center addObserverForName: kNetworkWentByeByeNotification
object: nil
queue: [NSOperationQueue mainQueue]
usingBlock: ^(NSNotification *notification) {
NSLog (@"Network went down: %@", notification);
}];
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):
- (void) networkChanged: (NSNotification *) notification {
// do stuff
if ([notification.name isEqualToString: kNetworkWentByeByeNotification]) {
NSLog (@"Network went away");
// update timestamp label
}
} // networkChanged
The object associated with the notification is the second piece of information held by the NSNotification object. This is provided by the code that posts the notification:
// NetworkMonitor
[center postNotificationName: kNetworkWentByeByeNotification
object: self
userInfo: userInfo];
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:
- (void) tapcasterTapped: (NSNotification *) notification {
if (notification.object == self.shoesizeTapcaster) {
// Upload the shoe size.
} else if (notification.object == self.bloodtypeTapcaster) {
// Take a blood sample from the user.
}
} // tapcasterTapped
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.
It’s good form to provide symbolic constants for the keys:
extern NSString *const kNetworkInterfaceKey; extern NSString *const kNetworkDownTimestampKey; extern NSString *const kNetworkAddressKey; extern NSString *const kNetworkPortKey; extern NSString *const kNetworkUptimeSecondsKey; extern NSString *const kNetworkBonjourKey;
That way you eliminate “typos” as a cause for programmer pain and suffering.
Then when posting the notification, cook up a user info dictionary:
NSDictionary *userInfo =
[NSDictionary dictionaryWithObjectsAndKeys:
self.interface, kNetworkInterfaceKey,
[NSDate date], kNetworkDownTimestampKey,
self.connectionAddress, kNetworkAddressKey,
self.connectionPort, kNetworkPortKey,
[NSNumber numberWithInt: timeDelta], kNetworkUptimeSecondsKey,
self.bonjourName, kNetworkBonjourKey,
nil];
// Post notification.
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName: kNetworkWentByeByeNotification
object: self
userInfo: userInfo];
(Objective-C literals will make this look soooo much nicer)
And then you can pick out the information you want when handling the notification:
NSString *interface = [notification.userInfo objectForKey: kNetworkInterfaceKey]; NSLog (@"interface is %@", interface);
Time out for Design
Like absolutely everything else involved in programming, you can apply rules and conventions to stuff that doesn’t necessarily need it.
When I declare notifications and keys, I typically declare them together, with the keys that go along with a notification underneath the notification:
extern NSString *const kNetworkWentByeByeNotification; extern NSString *const kNetworkInterfaceKey; extern NSString *const kNetworkDownTimestampKey; extern NSString *const kNetworkAddressKey; extern NSString *const kNetworkPortKey; extern NSString *const kNetworkUptimeSecondsKey; extern NSString *const kNetworkBonjourKey;
Suffixing the constant with “Notification” and “Key” show how they relate to each other.
For the actual string values, there’s (at least) two camps. And I waffle between the two.
One idea is to have keys be very readable:
NSString *const kNetworkWentByeByeNotification = @"network went bye bye"; NSString *const kNetworkInterfaceKey = @"network interface"; NSString *const kNetworkDownTimestampKey = @"network down timestamp"; NSString *const kNetworkAddressKey = @"network address"; NSString *const kNetworkPortKey = @"network port"; NSString *const kNetworkUptimeSecondsKey = @"network uptime seconds"; NSString *const kNetworkBonjourKey = @"bonjour key";
That way the dictionary is very easy to visually parse when printed out:
{
"bonjour key" = Nerdinalia;
"network address" = "192.168.254.13";
"network down timestamp" = "2012-07-16 19:52:19 +0000";
"network interface" = en1;
"network port" = 666;
"network uptime seconds" = 10;
}
The other is to have the names match the constant you’d type in to access the dictionary:
NSString *const kNetworkWentByeByeNotification = @"kNetworkWentByeByeNotification"; NSString *const kNetworkInterfaceKey = @"kNetworkInterfaceKey"; NSString *const kNetworkDownTimestampKey = @"kNetworkDownTimestampKey"; NSString *const kNetworkAddressKey = @"kNetworkAddressKey"; NSString *const kNetworkPortKey = @"kNetworkPortKey"; NSString *const kNetworkUptimeSecondsKey = @"kNetworkUptimeSecondsKey"; NSString *const kNetworkBonjourKey = @"kNetworkBonjourKey";
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:
{
kNetworkBonjourKey = Nerdinalia;
kNetworkAddressKey = "192.168.254.13";
kNetworkDownTimestampKey = "2012-07-16 19:52:49 +0000";
kNetworkInterfaceKey = en1;
kNetworkPortKey = 666;
kNetworkUptimeSecondsKey = 10;
}
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:
NSNotificationCenter *center;
center = [NSNotificationCenter defaultCenter];
token = [center addObserverForName: nil
object: nil
queue: nil
usingBlock: ^(NSNotification *notification) {
QuietLog (@"NOTIFICATION %@ -> %@",
notification.name, notification.userInfo);
}];
The block API is really easy for stand-alone handlers like this. If you intend on removing this handler you’ll want to hang on to the returned token and pass it to removeObserver:.
This is pretty cool. Here’s some of the stuff it prints on launch:
NOTIFICATION NSWillBecomeMultiThreadedNotification -> (null)
NOTIFICATION _UIWindowDidCreateWindowContextNotification -> {
"_UIWindowContextIDKey" = "-464166441";
}
...
NOTIFICATION UIWindowDidBecomeVisibleNotification -> (null)
NOTIFICATION UIDeviceOrientationDidChangeNotification -> {
UIDeviceOrientationRotateAnimatedUserInfoKey = 1;
}
NOTIFICATION UIWindowDidBecomeKeyNotification -> (null)
NOTIFICATION UIApplicationDidFinishLaunchingNotification -> (null)
NOTIFICATION UIApplicationDidBecomeActiveNotification -> (null)
NOTIFICATION _UIApplicationDidEndIgnoringInteractionEventsNotification -> (null)
...
NOTIFICATION UINavigationControllerDidShowViewControllerNotification -> {
UINavigationControllerLastVisibleViewController =
"<GRMainMenuViewController: 0x255320>";
UINavigationControllerNextVisibleViewController =
"<GRClassChooserViewController: 0x2e0d80>";
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:
center = [NSDistributedNotificationCenter defaultCenter];
token = [center addObserverForName: nil
object: nil
queue: nil
usingBlock: ^(NSNotification *notification) {
QuietLog (@"DISTRIBUTED %@ -> %@",
notification.name, notification.userInfo);
}];
You can see distributed notifications when tracking menus twiddling stuff in the system preferences:
DISTRIBUTED com.apple.HIToolbox.beginMenuTrackingNotification -> {
ToolboxMessageEventData = 145;
}
DISTRIBUTED com.apple.HIToolbox.endMenuTrackingNotification -> (null)
DISTRIBUTED AppleShowScrollBarsSettingChanged -> (null)
DISTRIBUTED com.apple.HIToolbox.beginMenuTrackingNotification -> {
ToolboxMessageEventData = 25921;
}
DISTRIBUTED com.apple.HIToolbox.endMenuTrackingNotification -> (null)
DISTRIBUTED AppleSideBarDefaultIconSizeChanged -> (null)
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:
center = [[NSWorkspace sharedWorkspace] notificationCenter];
token = [center addObserverForName: nil
object: nil
queue: nil
usingBlock: ^(NSNotification *notification) {
QuietLog (@"WORKSPACE %@ -> %@",
notification.name, notification.userInfo);
}];
And then spy on some of the workspace notifications:
WORKSPACE NSWorkspaceDidActivateApplicationNotification -> {
NSWorkspaceApplicationKey =
"<NSRunningApplication: 0x7ff56aa01230 (com.literatureandlatte.scrivener2 - 25588)>";
}
WORKSPACE NSWorkspaceDidDeactivateApplicationNotification -> {
NSWorkspaceApplicationKey =
"<NSRunningApplication: 0x7ff568d09970 (com.apple.Terminal - 145)>";
}
WORKSPACE NSWorkspaceWillSleepNotification -> (null)
WORKSPACE NSWorkspaceDidWakeNotification -> (null)
WORKSPACE NSWorkspaceDidTerminateApplicationNotification -> {
NSApplicationName = "backupd-helper";
NSApplicationPath =
"/System/Library/CoreServices/backupd.bundle/Contents/Resources/backupd-helper";
NSApplicationProcessIdentifier = 25947;
NSApplicationProcessSerialNumberHigh = 0;
NSApplicationProcessSerialNumberLow = 3703688;
NSWorkspaceApplicationKey =
"<NSRunningApplication: 0x7ff568e12da0 ((null) - 25947)>";
NSWorkspaceExitStatusKey = 0;
}
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.
(Tune in Thursday for the final part, gotchas.)


6 Comments
Any idea why Apple (or Next) chosen NSString constants to identify notification? In sake of self documentation but in detriment of efficiency? Comparing integers is much more efficient than comparing strings. May be such approach makes coincidences of notifications ‘codes’ in applications and frameworks less probable?
There’s actually no loss of efficiency. You can “intern” a string, which guarantees that a particular string will have a unique address (as far as say things like notification names), so what used to be string equality checks (order N)turn into pointer equality checks (constant time – so just the four or eight bytes to store the pointer value is used), which ends up being the best of all worlds – documentation, lack of collisions (otherwise you have to be very careful carving up ranges of integers, and debugability (being able to look at stuff in memory and see a string, vs a bare integer you have to know how to interpret), as well as performance.
Do you really think runtime compares pointers instead of strings themselves dispatching notifications? May be it (runtime) compares pointers first. If pointers are equal, strings are equal as well. If pointers are not equal actual string compare should be done. So deciding whom to despatch particular notification notification center looks for coincidence of pointer in array with sample. If coincidence not found, consecution of actual string compare should be done. This approach seems to be as safe as efficient.
I got what you mean by “interning stringsâ€. Notification centre in any particular time knows which notifications types it is working with. So actually it could maintain it’s own table of notification types and actually do pointer lookup dispatching notification. Leaving meaningful notification names for programmers in sake of readability and ‘debuggability’.
Granted, I’m not privy to NSNotificationCenter’s implementation (unless I want to disassemble it
, but a previous toolkit I worked on (Galaxy, in the early 90s) used interned strings for a ton of stuff, including objective-C style message dispatching and for dictionary keys. It was pretty slick.
This was quite helpful. Thanks!