Typed notifications using custom extensions

The Swift API contains a lot of notifications sent out by the system like NSManagedObjectContextObjectsDidChange in Core Data or the AppDelegate events like UIApplicationDidBecomeActive. Some of these notifications contain rich data in their user info dictionary. Reading the user info data using typed notifications can clean up your code, especially if you use these notifications on multiple places throughout your code.

Extension compared to custom classes are better discoverable by the implementors and therefore more likely to integrate.

Creating the extension

It’s often a good idea to write an extension on top of the Swift API to write custom solutions for readability. Extensions compared to custom classes are better discoverable by the implementors and therefore more likely to integrate.

Our extension is on top of NotificationCenter and makes use of a custom protocol called NotificationRepresentable.

extension NotificationCenter {
    /// Adds an observer using the given representable type to parse the notification to typed data.
    func addObserver<T: NotificationRepresentable>(for representableType: T.Type, object obj: Any?, queue: OperationQueue?, using block: @escaping (T) -> Swift.Void) -> NSObjectProtocol {
        return addObserver(forName: T.name, object: obj, queue: queue) { (notification) in

            // Parse the user info to the representable type.
            let notificationRepresenter = T(notification: notification)
            block(notificationRepresenter)
        }
    }
}

The protocol used in this extension contains a required initialiser which makes use of the notification and the related notification name which is used to observe for.

/// A representable for notifications containing rich data in their user info.
protocol NotificationRepresentable {
    /// The related notification name for which this representable is able to parse data.
    static var name: Notification.Name { get }

    /// Creates a new representer using the given notification data.
    ///
    /// - Parameter notification: The posted `Notification` which is used to parse into rich typed data.
    init(notification: Notification)
}

Writing a notification representer

A good use case for creating a typed notifications is the NSManagedObjectContextObjectsDidChange notification. A representer includes the inserted, deleted, updated and refresh objects.

final class NSManagedObjectContextChanges: NotificationRepresentable {
    static let name = Notification.Name.NSManagedObjectContextObjectsDidChange

    let managedObjectContext: NSManagedObjectContext
    let insertedObjects: Set<NSManagedObject>
    let updatedObjects: Set<NSManagedObject>
    let refreshedObjects: Set<NSManagedObject>
    let deletedObjects: Set<NSManagedObject>

    init(notification: Notification) {
        managedObjectContext = notification.object as! NSManagedObjectContext

        insertedObjects = notification.userInfo?[NSInsertedObjectsKey] as? Set<NSManagedObject> ?? []
        updatedObjects = notification.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject> ?? []
        refreshedObjects = notification.userInfo?[NSRefreshedObjectsKey] as? Set<NSManagedObject> ?? []
        deletedObjects = notification.userInfo?[NSDeletedObjectsKey] as? Set<NSManagedObject> ?? []
    }
}

This representer will parse the user info into a class containing all changes in typed sets and makes the code on implementation level a lot more readable.

Using the extension

A new method is available on top of the NotificationCenter class which makes it really easy to use. For our example, we could monitor the amount of inserted items using a print statement.

NotificationCenter.default.addObserver(for: NSManagedObjectContextChanges.self, object: managedObjectContext, queue: nil) { (changes) in
    print("The change contained \(changes.insertedObjects.count) inserts")
}

The use case for writing these extensions is especially useful when using notifications in multiple places throughout your project. However, this is just an example as writing extension on top of other Swift APIs can also improve your code a lot. The great benefit of extensions is that they are easily discoverable on top of default Swift APIs.