Unregistering NSNotificationCenter Observers in iOS 9

A reminder of something that Apple sneaked into the Foundation Release Notes for iOS 9 and OS X 10.11. It is no longer necessary for an NSNotificationCenter observer to un-register itself when being deallocated. There are some caveats if you need to support iOS 8 or use block based observers. I forgot this while writing last weeks post on detecting low power mode so here to jog my memory are the details along with a bonus tip.

NSNotificationCenter Changes in iOS 9

Registering an observer for an NSNotificationCenter notification is a common task in iOS and OS X. Here is a typical line of code you might use in the viewDidLoad method of a view controller to receive notification when the user changes the preferred font size:

NSNotificationCenter.defaultCenter().addObserver(self,
  selector:#selector(didChangePreferredContentSize(_:)),
  name: UIContentSizeCategoryDidChangeNotification, 
  object: nil)

Using iOS 8 or earlier you need to unregister for this notification before deallocating the observer object. If you forget you are at risk of a crash when the notification center sends the next notification to an object that no longer exists.

Using iOS 9 or later the Foundation framework release notes contain some good news:

In OS X 10.11 and iOS 9.0 NSNotificationCenter and NSDistributedNotificationCenter will no longer send notifications to registered observers that may be deallocated.

The notification center now keeps a zeroing reference to the observer:

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.

So the next time the notification center wants to send a notification to the observer it can detect that it no longer exists and remove the observer for us:

This means that observers are not required to un-register in their deallocation method. The next notification that would be routed to that observer will detect the zeroed reference and automatically un-register the observer.

Note that this does not apply if you are using a block based observer:

Block based observers via the -[NSNotificationCenter addObserverForName:object:queue:usingBlock] method still need to be un-registered when no longer in use since the system still holds a strong reference to these observers.

Also if you prefer, or need to support iOS 8 or earlier you can still remove the observer:

Removing observers (either weakly referenced or zeroing referenced) prematurely is still supported.

To be clear if you support iOS 8 or earlier do not forget to remove the observer in the deinit method:

deinit {
  NSNotificationCenter.defaultCenter().removeObserver(self, 
    name: UIContentSizeCategoryDidChangeNotification, 
    object: nil)
}

Debug Description

This is another bonus in the Foundation release notes that makes it easier to list the observers registered with the notification center:

NSNotificationCenter and NSDistributedNotificationCenter will now provide a debug description when printing from the debugger that will list all registered observers including references that have been zeroed out for aiding in debugging notification registrations.

(lldb) p NSNotificationCenter.defaultCenter().debugDescription
(String) $R10 = "<NSNotificationCenter:0x134e0cb10>\nName, Object, Observer,  Options
UIAccessibilityForceTouchSensitivityChangedNotification, 0x19b0bbb60, 0x134d5d2e0, 1400
UIAccessibilityForceTouchSensitivityChangedNotification, 0x19b0bbb60, 0x134d605f0, 1400
...
UIContentSizeCategoryDidChangeNotification, 0x19b0bbb60, 0x134e5c2a0, 1400

You may find the number of notifications your application is observing surprising! There are so many that you will most likely need to increase the max length of the output string to see them all in the LLDB console:

(lldb) set set target.max-string-summary-length 50000

Further Reading