Blog

Functional Snippet #16: Typed Notification Observers

For this snippet, we’ll build upon NSNotificationCenter. We will improve two things: first, we will make sure the adding and removing observers to the notification center is automatically managed. Adding is as simple as creating an object, and removing is as simple as deallocing that object. The second improvement we’ll make is having our notifications typed, using phantom types.

First, let’s create a struct to describe notifications. Note that this struct has a phantom type: A will be the type of objects that the notification carries in its userInfo. The notification self only holds the string value containing the name:

struct Notification<A> {
    let name: String
}

To post a notification, we simply pack up the value in the user info. Because of the way NSNotificationCenter works, we need to box up the value (in case it was not an object):

func postNotification<A>(note: Notification<A>, value: A) {
    let userInfo = ["value": Box(value)]
    let center = NSNotificationCenter.defaultCenter()
    center.postNotificationName(note.name, object: nil, userInfo: userInfo)
}

Finally, we can create our observer object. Creating an instance of this object adds an observer to NSNotificationCenter, and when it deallocs, it removes itself from the notification center. This allows us to store it as a property on an object, and once the property gets set to nil (e.g. when the object deallocs), it automatically removes itself from the notification center.

class NotificationObserver {
    let observer: NSObjectProtocol

    init(notification: Notification<A>, block aBlock: A -> ()) {
        let center = NSNotificationCenter.defaultCenter()
        observer = center.addObserverForName(notification.name, 
                                             object: nil, 
                                             queue: nil) { note in
            if let value = (note.userInfo?["value"] as? Box<A>)?.unbox {
                aBlock(value)
            }
        }
    }

    deinit {
        NSNotificationCenter.defaultCenter().removeObserver(observer)
    }

}

We have all the pieces to start using our typed notifications. For example, let’s say we want to be able to have a globalPanicNotification, which has an NSError associated with it:

let globalPanicNotification: Notification<NSError> = Notification(name: "Global panic")

Sending this notification is as simple as calling postNotification:

let myError: NSError = NSError(domain: "io.objc.sample", code: 42, userInfo: [:])
postNotification(globalPanicNotification, myError)

Note that, because of the phantom type, we can’t pass in any other value than NSError. To observe values of this error, we can simply create an instance variable in our class:

let panicObserver = NotificationObserver(notification: globalPanicNotification) { err in
     println(err.localizedDescription)
}

That’s all there is to it. We now have type-safe notifications that get automatically added and removed to and from the notification center, without having to worry about it. We can use all the fantastic infrastructure provided by the frameworks, and gain a lot of safety by creating tiny wrappers around it. The full code is available as a gist.

Stay up-to-date with our newsletter or follow us on Twitter.

Back to the Blog

recent posts