NSNotificationCenter is Probably an Anti-Pattern

This week’s iOS Dev Weekly linked to a tweet from Ben Sandofsky about an important change to NSNotificationCenter in iOS 9:

In OS X 10.11 and iOS 9.0 NSNotificationCenter and NSDistributedNotificationCenter will no longer send notifications to registered observers that may be deallocated. If the observer is able to be stored as a zeroing-weak reference the underlying storage will store the observer as a zeroing weak reference, alternatively if the object cannot be stored weakly (i.e. it has a custom retain/release mechanism that would prevent the runtime from being able to store the object weakly) it will store the object as a non-weak zeroing reference. This means that observers are not required to un-register in their deallocation method. Link.

I can’t remember where I heard the following fact (I think it was during one of the WWDC 2015 talks), but it’s my understanding that a deallocated-without-unregistering NSNotificationCenter observer is one of the top causes of crashes in third-party iOS/OS X software. Assuming this is true, a thought struck me today: how much less common would this crash have been if there had been an easier alternative than KVO all this time?

In Foundation, there are only two first-party ways to propagate model changes from one to many observers: KVO and NSNotificationCenter. Both of them are prone to cause crashes when misused, but I’d argue that KVO is harder to use correctly. It’s not unreasonable to imagine that many developers opt for NSNotificationCenter as the least bad option.

It may sound surprising to some that I’d refer to NSNotificationCenter as a “bad” option. I should clarify that I think NSNotificationCenter is a fine API when used for events of a truly global nature. An argument for or against NSNotificationCenter is like an argument for or against a singleton. As I’ve joked on Twitter:

Use a singleton when you need the Sun, not an overhead lamp.

Similarly, I think NSNotificationCenter is best suited for events whose origin is so far outside the scope of local control flow that it would be awkward or even silly to use anything else. The NSNotificationCenter version of my singleton tweet is:

NSTheSunDidRiseNotification, not NSSomebodysPassengerPushedTheFlightAttendantButtonNotification.

In practice I have a hard time justifying the use of NSNotificationCenter for anything besides events vended by the OS, like UIKeyboardWillShowNotification.

I often see NSNotificationCenter used as a clearing house for all the one-to-many relationships between model objects and observers in an app. I think this is probably an anti-pattern. This pattern obligates observers to decide when to ignore notifications.

Consider a Twitter client with a TweetWasLikedNotification. The observers for that notification not only need to know the ID of the liked tweet, but also the local account from which that like action was taken, and/or perhaps the NSManagedObjectContext to which the tweet belongs, etc. Global knowledge belongs at a higher layer in the application. Better still is a design that couples model and observer more closely with an explicit relationship (perhaps a delegate protocol) that doesn’t require such knowledge in the first place.

I have considered using a custom center, instead of the defaultCenter() singleton, but that seems like a half-baked implementation of a design that would be better expressed via a weak-referencing collection of SomeCustomObserverProtocol delegates.

Until Apple provides a Swift-native, first-party way to implement a one-to-many observer pattern, I will continue to use NSObject subclasses and KVO. I’ve been trying to think of ways to abstract out the particulars of NSObject/KVO relationships, keeping the higher-level APIs cleaner and more friendly. Here’s an example of what I mean, posted to GitHub. For a direct-download link of the Xcode playground, click here. I haven’t tried this in production yet, but the idea is to map NSObjects to adapter/observers who produce “view model” structs in response to KVO updates, passing those structs onto their delegates. This lets me use value types instead of the actual model objects for UI updates, without having to abandon KVO as the underlying infrastructure.

|  1 Jan 2016