The observer pattern is a powerful way to decouple the sending and handling of events between objects in a system. On iOS, one implementation of this pattern is via NSNotificationCenter. However, the
NSNotificationCenter APIs are kind of cumbersome to use and require some boilerplate code. Luckily, Swift gives us the tools to improve
NSNotificationCenter with very little code.
Out with the old
A while back, objc.io posted functional snippet 16, on Typed Notification Observers. I had already been working on a generic, reusable way to observe notifications in iOS, but this snippet really pointed me in the right direction. One major motivation here is to remove the boilerplate of handling notifications. A typical example would be a view controller that registers to observe a notification and implements an instance method to be called when the notification is received.
This is problematic for a few reasons.
- We need to remember to explicitly add and remove
selfas an observer. Forgetting to remove
deallocis a bug.
- The code that actually handles the notification is in a totally different area (the
handleNotification:method). As more code is added to this class, it becomes more difficult to see what is happening and when.
- Because this is Objective-C, there is no type-safety. An
NSNotificationcan have an object (
id) property and userInfo (
NSDictionary) property. If accessing the object, we must know how cast it. And if accessing the dictionary, we must know what it contains.
- We must duplicate this pattern across all parts of our app, proliferating issues (1), (2), and (3).
A new micro-library
Say hello to JSQNotificationObserverKit, a Swift framework based on snippet 16. This framework remedies the issues described above with a tiny API that is extremely flexible. As you’ll notice from the documentation, there is only 1 class, 1 struct, and 1 function. This is all we need, which is as awesome as it is surprising. Let’s see how it works.
We begin by creating a
Notification. This struct has two type parameters: a value
V and sender
S. The type of value
V that the notification sends is a phantom type, while
S is the type of sender associated with the notification. A
Notification also has a
name property and an optional
Next, we create our
NotificationObserver, which is initialized with the notification described above and a closure to be called when the notification is received. Instantiating this observer automatically adds it to
NSNotificationCenter for the notification
sender specified by the
Notification function parameter. Because
NotificationObserver has the same type parameters as
Notification, it can only observe that specific kind of notification.
Finally, we post the notification using the
postNotification(_:value:) function. Again, this function has the same type parameters as
Notification, which enforces sending a value of the type specified by the phantom type of
Notification. In this example, we can only send a
CGSize as the value. Anything else would result in a compiler error.
When the observer is set to
nil (when it is deallocated), then it is removed from
That’s all. Each of the aforementioned issues have been resolved. Registering and unregistering for a notification is now as simple as creating and destroying an observer object. The code for the registration and handling of the notification is all in one place. We have type-safety, and a reusable solution for the rest of our app.
Tiny, but flexible
So how flexible is this small API? The example above works for a notification with a sender and a value, but what about a notification that only has a value? Or a notification that only has a sender? Or a notification that has neither? What about supporting more typical Cocoa notifications that send a userInfo dictionary? We can do it all.
A notification can be configured in 4 different ways:
- It can have a specific sender and value (as in the example above)
- It can have only a sender. For example,
UIApplicationDidReceiveMemoryWarningNotificationdoes not send any data in the userInfo dictionary.
- It can only have a value. Sometimes observers do not care which object posted a given notification.
- It can have neither a sender, nor a value. Sometimes a notification may be posted to notify of an event, and either there isn’t an associated sender, or the observer does not care.
For notifications without a sender, or for which the observer does not care about the sender, we can specify a sender of type
AnyObject. When we initialize a
Notification, we can omit the
sender parameter which defaults to
nil. The semantics here are great. Any object can send this notification, it does not matter to the observer.
For notifications without a value, we simply specify a value type of
Void. Then when the notification is posted we send the empty tuple,
(). Remember, the empty tuple is equivalent to
From here, it is easy to see how we can construct and post a notification with neither a value nor a sender. Furthermore, the value type could be a
Dictionary which allows this API to conform to the existing patterns in Cocoa. For more examples on usage, see the unit tests included with JSQNotificationObserverKit.
Less is more
It really is incredible how much we can accomplish with so little — 1 class, 1 struct, and 1 function. An equivalent API in Objective-C would have been more than double the size and still not type-safe. I think the objc.io snippets are absolutely great, but they are often deceptively simple. The power and flexibility of these functional patterns is not always obvious to a tenured Objective-C developer. If you want to learn even more about micro-libraries, check out Chris Eidhof’s talk on Tiny Networking.