NSNotificationCenter part 1: Receiving and sending notifications

Perhaps because it’s often paired with the much maligned KVO, or because the delegate pattern is so ubiquitous, NSNotificationCenter is frequently overlooked and misunderstood. Yet when it comes to broadcasting notifications to any object willing to listen, NSNotificationCenter has all your bases covered, and does so with a minimal number of methods and classes.

If you ever found yourself bubbling up events via delegates, pondering on how to avoid retain cycles when implementing the observer pattern, or working around blocks to deal with users leaving and then returning to a new UIViewController instance, chances are there was a much cleaner, decoupled solution using notifications.

This article is the first part of a series about NSNotificationCenter. Part 1 covers how to receive and send notifications while addressing common misconceptions. Part 2 provides a typed implementation of the observer pattern with notifications. Part 3 shows how to unit test notifications with a little help from OCMock. Part 4 deals with asynchronous notifications using NSNotificationQueue. Finally, a list of all public notifications in iOS and OS X are included as annexes.

While iOS classes will be used for examples, everything applies to OS X as well.

Notifications classes

Foundation provides 3 classes to deal with all your notification needs:

  • NSNotification: represents a notification.
  • NSNotificationCenter: broadcasts notifications and manages observers. Each app has a default notification center via defaultCenter.
  • NSNotificationQueue: coalesces and delays notifications. Each thread has a default notification queue via defaultQueue.

OS X offers a fourth class called NSDistributedNotificationCenter for communication between processes. We will not cover this class in this series.

Receiving notifications

To receive a notification you only need to know its name. Cocoa and Cocoa Touch are full of interesting and descriptively named notifications such as UIKeyboardWillShowNotification and UIApplicationDidReceiveMemoryWarningNotification. There are 165 public notifications in iOS 7.0 (annex A) and 393 in OS X 10.9 (annex B). Later we will see how to create your own notifications as well.

Receiving a notification takes very little code. Say you want to receive a memory warning (UIApplicationDidReceiveMemoryWarningNotification) outside a UIViewController, to clear a cache of some object. This is how it could look:

Adding the observer

The object that receives notifications is called observer, and must be added to a NSNotificationCenter. Unless you have a very strong reason not to, that will always be the defaultCenter.

init and viewDidLoad and viewWillAppear: in UIViewController are good candidates for adding observers. You should add the observer as late as possible and remove it as soon as possible to improve performance and avoid undesired side effects.

Observers register to a specific notification by specifying the name of the notification (usually a constant) and the sender of the notification as filters. Both are optional; in the above example, we don’t care who the sender is so we pass nil.

Specifying the sender is useful when different instances are sending notifications of the same name (e.g., you might be interested in the UITextFieldTextDidChangeNotification of a specific UITextField). If no sender is provided, you will receive all notifications with the given name. If no notification name is specified, you will receive all notifications of the given sender.

Handling the notification

Observers will receive notifications until they are removed from the NSNotificationCenter. Each notification is sent once per addObserver call. If you are receiving more notifications than expected, it might be a sign that you added the observer more than once (e.g., this might happen if you register the observer for notifications in viewWillAppear and unregister it in dealloc).

NSNotificationCenter will call the selector provided in addObserver:selector:name:object: with a single argument of type NSNotification. The selector is called in the same thread in which the notification was posted. Typically this is the main thread, so only check if the selector is running in a different thread if the corresponding documentation says otherwise.

The NSNotification object is a box of chocolates. It comes with an object id which is usually the sender of the notification and an userInfo dictionary with additional information about the notification. For example:

Knowing the contents of this dictionary for a specific notification might require debugging or -god forbid- reading the documentation. Don’t assume that any particular key will be present unless told otherwise.

You can also handle notifications with blocks to make unit test enthusiasts cringe. The initial example becomes:

The queue parameter is for asynchronous notificaions and is explained in part 4.

Removing the observer

The observer must be removed eventually, and you should do it as soon as you don’t need the notifications anymore. This is at best right after receiving the notification (e.g., to update the UI after MPMoviePlayerPlaybackDidFinishNotification) or at worst in dealloc. If you forget to remove the observer, NSNotificationCenter might send a notification to a deallocated instance, causing shenanigans.

NSNotificationCenter provides two methods to remove observer. removeObserver: and removeObserver:name:object:. The former unregisters the observer for all notifications, and the latter does so for notifications of the given name and sender, either of which can be nil. While it might be tempting to simply call removeObserver:, this is a bad practice as you might be unregistering from notifications registered elsewhere (e.g., in a superclass, subclass or category). Try to be as specific as possible when removing the observer.

As shown above, you also need to remove the observer when using blocks. In this case the observer is the object returned by addObserverForName:object:queue:usingBlock:. With these block observers you can use the single parameter removeObserver: more comfortably.

Sending synchronous notifications

Notifications are great to broadcast events that might be of general interest, such as model changes, user actions or the status of background processes. Unlike the delegate pattern, notifications can be used to notify various stakeholders and add very little coupling (in most cases, the observer only needs to know the notification name).

Naming notifications

The first step in creating your own notification is choosing a unique name. The document Coding Guidelines for Cocoa suggests:

For example:

  • Good: @"HPEAudioPlayerDidStartPlaybackNotification"
  • Bad: @"didStartPlayback"

The notification name should be a public constant. This will allow you to change the name of the notification without requiring any changes in the observers.

Posting the notification

Each app has a default NSNotificationCenter instance that can be used to broadcast notifications. You can create the notification object yourself and post it, or use a couple of convenience methods that do this. If you don’t need to send any additional information, posting a notification is as simple as:

The object parameter is typically used to pass the object sending the notification, normally self. NSNotificationCenter will create a NSNotification object for the given name and the given sender, and no additional information.

The notification is then sent to every registered observer in no defined order. Notifications are posted synchronously, meaning that postNotification: and its convenience methods will not return until all observers have processed the notification. Additionally, observers will be called in the same thread in which the notification is posted. Unless you explicitly state otherwise, observers will expect this to be the main thread.

Consider a background operation that notifies progress periodically. We want to send the notification and continue with our operation as soon as possible, and notify the observers in the main thread. A way to do this is with dispatch queues:

Sending additional information

In most cases you will want to send additional information with the notification, such as the progress value in the example above. Notifications can have a dictionary with custom data called userInfo. NSNotifcationCenter offers a convenience method to post notifications with userInfo:

Again, you should define any userInfo keys as public constants.

Subclassing NSNotification

If the userInfo dictionary does not suit your needs or if you prefer typed atributes you can also subclass NSNotification, with a few caveats. NSNotification is a Cocoa class cluster, which is another way of calling an abstract class. In particular, it does not implement name, object and userInfo and throws an exception if you call init.

Because of these caveats, subclassing NSNotification is generally frowned upon and some developers prefer to use categories. Full examples of both approaches are provided in part 2 where we implement a typed observer with notifications.

When using your own NSNotification subclass, the convenience methods to post the notification are no good. You are responsible for creating the notification object, and provide the name, object and userInfo (if applicable) either in the subclass implementation or before calling postNotification:. For example:

Further reading

2 thoughts on “NSNotificationCenter part 1: Receiving and sending notifications

  1. Pingback: NSNotificationCenter part 2: Implementing the observer pattern with notifications | Hermes Pique

  2. Pingback: NSNotificationCenter part 4: Asynchronous notifications with NSNotificationQueue | Hermes Pique

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">