Notifications, part 1 - Registering and Posting

Mark Dalrymple's Headshot
Mark Dalrymple Cocoa iOS

Notifications on the brain. Last time was a bug report about something I stumbled across when debugging some notification-related stuff. Before pursuing that much farther, I figured I’d talk about notifications.

Notifications in Cocoa are a decoupling mechanism. You have an object, say a network monitor, that wants to tell other objects that something happened, such as the network connection went down. There’s a bunch of ways you can implement this kind of behavior.

You could subclass the network listener and override handleDisconnect. You could set up a target / action for the network monitor to invoke a particular selector on a provided object. You could use the responder chain. You could set up a delegate relationship. You could also use notifications.

Most of the options in that list are 1-to-1, such as there being one delegate or data source that’s used by a table view, or one target/action pair that’s used by an NSButton.

Notifications are a 1-to-many relationship. Even better, a 1-to-many relationship without a direct connection between the object broadcasting the notification, and the objects that are notified. You could implement an object could with an NSArray of delegate objects which it walks through to do delegate-like stuff. This object knows exactly who the delegates are. With notifications, the notification center acts as a layer of indirection between the object that’s reporting “Hey! Lost the network connection!” and all the objects that want to react to the network going down:

Notification 1 1

The three objects on the right (delegate, data viewer, and the logging system) all told the notification center that they were interested in finding out when the network goes down. When it comes time to tell the world that the network went down, the network monitor tells the notification center to tell interested parties. There is no direct connection between the one broadcasting a notification and the ones reading it. Fancy people consider it an implementation of the Observer Pattern.

The notification center acts as an intermediary, freeing objects that want to broadcast a message to the world from keeping their own list of interested observers. It also means that the broadcasting objects don’t have to be of a particular class or adopt a particular interface (which is a popular implementation style in other toolkits). Given Objective-C’s runtime machinery, you can send messages using arbitrary selectors, so the observing objects don’t have to be of particular classes, interfaces, or have to implement a particularly-named callback function.

How’s it Work?

Objects interested in receiving notifications tell a notification center of their interest. The notification center adds these objects, and their callback information, to an internal list, kind of like an address book. When your program starts, the notification center is pretty empty:

Notification 1 2

Then an object comes along expressing interest in being notified.

// AppDelegate
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver: self
        selector: @selector(networkByeBye:)
        name: kNetworkWentByeByeNotification
        object: self.networkMonitor];

The AppDelegate is telling the notification center that it wants to know when the network goes away, perhaps so it can schedule a reconnect in the future. In particular, the AppDelegate is interested in a notification named kNetworkWentByeByeNotification, which is just an NSString. If a notification by that name gets posted by the networkMonitor, send the message networkByeBye: to self (the AppDelegate). The notification center jots down the address of the app delegate, the address of the network monitor, as well as the selector, and sticks it under a rock labeled kNetworkWentByeByeNotification

Notification 1 3

Then another object comes along, this time the data view. It wants to know when the network goes down so it can update the user interface. It registers interest in a similar way:

// DataViewer
[center addObserver: self
        selector: @selector(handleNetworkChange:)
        name: kNetworkWentByeByeNotification
        object: nil];

Same kind of drill with the app delegate. This time it specifies a nil object. This means that no matter who posts the network-bye-bye notification, the data viewer will get it. The AppDelegate, though, will only get bye-bye notifications from a specific network monitor.

The notification center grabs another piece of paper, jots down the address of the data viewer, the name of the selector to trigger, and the filter for the broadcasting object (in which case is nil), and hides it under the kNetworkWentByeByeNotification rock:

Notification 1 4

Finally, the logging system wants to register itself. This developer used a more modern API, using a block and an operation queue:

// LogOTron
// _token is an instance variable of type 'id'
_token = [center addObserverForName: kNetworkWentByeByeNotification
                 object: nil
                 queue: [NSOperationQueue mainQueue]
                 usingBlock: ^(NSNotification *notification) {
                     NSLog (@"Network went down: %@", notification);

This tells the notification center, “hey, whenever someone posts the bye-bye notification, I want you to toss this block onto this queue.” The notification center tears off another piece of paper and makes some notes, in particular the object to filter notifications with (in this case, nil means all objects posting the notification), the queue to run the blocks on, and the block itself. It hides this note under the kNetworkWentByeByeNotification rock as well.

Notification 1 5

One interesting consequence of this newer call is that you can register a notification handler without having a corresponding class and object. The old-school API sends a message to an object. If you wanted to observe something you had to have some object the notification center could send a message to. With the block API, you just give it a block. No need for a supporting object.

Now What?

Everyone is now registered. Has anything actually happened? Not yet. The notification center just has a list of interested parties, but they haven’t been told anything. The action starts when someone posts a notification.

The network goes down. The network monitor wants everybody to know:

// NetworkMonitor
- (void) networkWentDown {
    // Collect the error
    // Pack the error and other information into a dictionary
    NSDictionary *userInfo = ...;

    // Post notification.
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center postNotificationName: kNetworkWentByeByeNotification
            object: self
            userInfo: userInfo];

} // networkWentDown

The last call tells the notification center to post the network bye-bye notification to all interested parties. The notification center looks under the rock labeled kNetworkWentByeByeNotification and looks at each of the pieces of paper it hid under there. If the object should be notified, the given selector is used to send a message to the object, or the block is put on a queue.

The AppDelegate will get notified by having its networkByeBye: method called. The LogOTron will get notified by having its block scheduled onto the main queue.

The DataView will get notified via its handleNetworkChange: method only if the network monitor posting the notification is the same (which is an identity check, not an equality check).

Unregistering From Notifications

When an object is no longer interested in receiving notifications, it should unregister itself from the notification center. The notification center does not retain the observer object and the filtering object, so it’s possible for the observer object to be destroyed and the notification center holding on to a dangling reference. It does retain the block when using the block based API, so watch out for retain cycles (more about that later). You should unregister yourself as soon as you know you’re not interested in the notification. You can also unregister in your dealloc so that there are no dangling references to your object.

It’s pretty easy to unregister from all notifications:

[center removeObserver: self];

This removes self from the notification center completely. For maximal safety, though, you should only unregister from the specific notifications you registered yourself for. There may be some other notifications that the object is listening to deep inside of Cocoa.

In the case of the AppDelegate and the DataViewer, they would call something

[center removeObserver: self
        name: kNetworkWentByeByeNotification
        object: nil];

This unhooks them from any bye-bye notifications, but leaves the rest of the notifications untouched.

The LogOTron case is a little more complicated. Because the notification doesn’t require a listening object, there’s not really a way to tell the notification center what it needs to remove from its stack of objects to notify. There’s nothing to grab on to. That’s why the block-based addObserverForName method returns a token. This is an opaque object to stash somewhere, and then pull it out when you need to unregister yourself from the notification.

Therefore, LogOTron would do something like this to unregister itself, using the token returned when the notification was registered:

[center removeObserver: _token];

(tune in next week when notification handling itself is covered, along with spying and some gotchas related to threading)

Are you looking for a partner in developing a Cocoa app? Do you want to accelerate your learning? Mark Dalrymple and the rest of the nerds can teach you the latest and greatest in Cocoa development.

Recent Comments

comments powered by Disqus