Slug michael helmbrecht foolproof notifications header

No More Typos: Foolproof Notifications in Swift

One of the most common ways for parts of your app to talk to each other is a Notification (RIP “NSNotification”). But the only way to send extra information is to put everything in a dictionary and hope all the keys and types line up on the other end. That loose behavior is heretical in Swift! Let’s use some clever language tricks to build strongly typed, foolproof notifications.

Intro (0:00)

My name is Michael Helmbrecht, and I am here to talk a little bit about No More Typos, Foolproof Notifications for a Happier You. I think this is a cool topic for a Slug Talk because it’s really small. This is not a talk on a new architecture for the new Hot Nest, or this is a new library; this is something that literally took me half an hour to reimplement. It’s not very complicated, but it’s really nice because it touches on a lot of language features that Swift offers that are improvements over the way that we would do things in Objective C. All of the code I will be writing in the demo portion is on GitHub

What Are Notifications?

Before we get started on foolproof notifications, I would like to cover what notifications are in the Apple ecosystem. I like to think of notifications as shouting into the void. If you’re posting a notification, it basically means you’re basically saying, “Hey, here’s the thing happening, just in case anyone cares.” There’s no guarantee anyone’s listening, you don’t know who’s listening, you’re just kind of shouting out there into the void. Maybe someone cares, maybe someone doesn’t.

On the receiving end, you’re just kind of sitting there waiting, very delicately, with your ear out the window, waiting for the right thing to happen.

It’s a communication mechanism, and it’s differentiated from other ones within the Apple ecosystem because it’s many-to-many, meaning anybody can talk to anybody else. There’s no restrictions on numbers or how many people can post the same notification, how many people can listen. This makes it a little bit different than a lot of other mechanisms and it’s really strongly decoupled. It allows you to not have any one piece of your app have to have intimate knowledge of another piece of your app. All they know is, oh, I’m posting notification; I don’t know what’s happening after that, or I’m listening, but I don’t know who’s generating that.

This is really useful in a lot of different scenarios that the delegate method or the target action pattern might not be useful for. If you somehow haven’t used these before, I am honestly impressed that you’ve managed to do this without ever seeing a notification, because they’re already used by the system. Really common ones include the keyboard appearing or disappearing. You can sign up for notifications when your app goes into the background or comes back out of the background.

What do notifications look like? (2:09)

We have Foundation.Notification. Long live NS Notification, but it’s gone now. To post a notification, is pretty straight forward, you get the notification center, which is the void into which one might shout or listen. Almost all of the time, you probably use the default notification center, but you can fabricate your own if you have a special reason for that. Next, you just say post and you give it a name so in this case, we’re petting a pug. You can optionally specify add object; what pug is being pet, or what object is generating this notification for example. You can also just say nil a lot of the times, which can be helpful for certain situations. The last thing is a dictionary called UserInfo. UserInfo is how you pass any kind of supplementary data along with your notification, so in this case, we’re passing, where are we petting this pug? Oh, we’re scratching its ear. The UserInfo can get kind of crazy being a dictionary.

Get more development news like this

On the other end, you listen to them by adding an observer. You need to know the same name, so in this case, we’re using the same name. Next you can use the object, in this case, as a filter mechanism, so you can say, “I only care about this one pug being pet.”

You can also check who’s sending this notification, and then you can optionally specify a queue, if you care about which queue it goes on. You can just say nil and it’ll fire in the main thread.

Then, you get a closure. Closures are great, we all love closures. You get the notification, and in this case, you see here that we’re looking at this UserInfo object where we’re checking to make sure it exists because it can be nil, we’re trying to pull something out of it, and then we’ll print it.

We can see some things that might be stumbling blocks here, which is why I tried to write these type notifications.

  • These names must exactly match.
    • If you have a typo, as with many stringly type DPIs, you will just never get a notification. If I mistype it, or I mis-remember the word, and I say pug.pat instead of pug.pet, that just will magically not work, and I will never get an error message, which is very deeply confusing. The stringly type DPI is always a danger sign.

Additionally, there is the UserInfo mechanism where there’s a dictionary but you can’t be sure ahead of time what’s in it. Obviously, if you write both of these, you know you put a string in over here, and it has this key and I get it out over there. Since its a stringly type, what if you mistype it, or what if in the sending a string, now I send some kind of struct about the data, then the listener’s just going to blow up on me. There’s lots of ways to accidentally make this really terrible for yourself.

When looking at these notifications, I felt this was a prime opportunity to write a Swift wrapper around it. Perhaps, we might like something like this for a notification to post, where we’re posting an enumeration, so I cannot typo this enumeration. The compiler will just say “I don’t know what pat is.”

In this case, it’s also an enum, which is great because I can’t typo that. If I try to put a struct in there, it just won’t compile, then, I can respond to a notification.

This is a similar system, where I’m adding an observer. I’m saying I care about the pet notification, and I’m getting this location in, and now this location is magically the type that I put in. So, I’m not unpacking a dictionary, I’m asking, “Is it here? Is it there? What kind of type is this thing I’m working with?” We can then do whatever we want with whatever kind of payload we put in there.

Next, let us construct such a thing, because it really is quite simple. First, I just like to have a little fun. So, I would like to define two things here.

A boop is when a pug “pokes” another with its paw. A blep is when a pug sticks it’s tongue out slightly. This may sound weird, but I’m all about a little fun, and you’ll see in the code what I use these for.

Demo (6:59)

Imagine we have an image of a pug. I’ve built some of this for us already, but what we are going to do is make it so you’ll be able to click on different little parts of the pug and say “You’re petting it on the ear,” or “You booped it on the nose.”

We’re going to have a delegate called a notifier, which will be producing notifications, and we have a printer just to log them out.

import UIKit
import PlaygroundSupport

// Image credit: https://www.pinterest.com/pin/475270566903222569/
let pugView = PugView(image: #imageLiteral(resourceName: "pug.jpg"), size: 500)
pugView.tapAreasShown = true

let pugViewDelegate = PugNotifier()
pugView.delegate = pugViewDelegate

let printer = NotificationPrinter()

PlaygroundPage.current.liveView = pugView

The view has a delegate, which checks if it appeared, and checks if it was tapped.

Let’s write this notification system.

We’re going to have a struct, and we’re going to keep some information because in our case, we’re going to count how many boops, we’re going to have some payloads with these things, so we’re going to have our count equal to zero.

private class PugModel {
    var isBlepping = false
    var boopCount: UInt = 0
}

We’ll have a private var pug equals PugModel.

private let pug = PugModel()

In this case, we can just print spot to prove to ourselves that we are indeed receiving the delegate method.

public func didTap(on spot: PugView.spot){
	print(spot)
}

These are the notifications

enum PugNotification {
	case arrive
	case blep(isBlepping: Bool)
	case boop(count: UInt)
	case pet(location: PetLocation)
	case leave(snack: SnackInfo)
}

We have a blank one. We have some with primitives. We have one with a struct that we have, or an enum that we have here. We also have a tuple as it’s payload.

Now we will complete the didTap func:

public func didTap(on spot: PugView.Spot) {
    switch spot {
    case .nose:
        pug.boopCount += 1
        PugNotification.post(.boop(count: pug.boopCount))
    case .mouth:
        pug.isBlepping = !pug.isBlepping
    case .ear:
        //pet
    case .back:
        //pet
    case .tummy:
        //pet
    case .tail:
        //leave
    }
}

When the pug is pet on its tail the pug has to leave because he’s so hungry.

Every time this comes in, we’re going to just fire off a notification.

Now, we’re going to actually write the thing that is important; a protocol that is typed. I originally had this as just straight on pug notification, and then I thought it would be cool if you just made it a protocol, and then you can use it on any type.

protocol TypedNotification {
	var name: String { get }
	var content: TypedNotificationContentType? { get }
}

We’ll have an extension that is typed. We’ll have static func post, and we will post a notification, and I want to post a self. The way we’re going to do this is just by wrapping notification center, but in order to post, we’re going to need to know some information.

  • we need to know a name,
  • we need to know an object,
  • we need to know what User Info we’re going to pass with this.

    extension TypedNotification { static func post(_ notification: Self) { let name = let userInfo = NotificationCenter.default.post(name: name, object: nil, userInfo: userInfo) }

When I was initially designing this, I had notification.name, but there’s no reason to let notification leak out of this. UserInfo was like a UserInfo dictionary, but due to some constraints that we’ll see later, you can only ever send one thing, so we’re going to just have that.

let userInfo = notification.content.map({ ["content": $0] })

It can be nil, so we use notification.content.map, which is really useful. If you haven’t used this as an optional, what it lets you do is if it exists, you do the thing, if it doesn’t exist, you don’t do the thing.

let name = Notification.Name(rawValue: notification.name)

Then we have notification.name, where we just pass in notification.name because you have to make one out of a string.

Now we will, and then we make pug notification conform.

extension PugNotification: TypedNotification {
	public var name: String {
    	switch self {
    	case .arrive:
        	return "pug.arrive"
    	case .blep:
        	return "pug.blep"
    	case .boop:
        	return "pug.boop"
    	case .pet:
        	return "pug.pet"
    	case .leave:
        	return "pug.leave"
    	}
	}

	public var content: TypedNotificationContentType? {
    	switch self {
    	case .arrive:
    	    return nil
    	case .blep(let isBlepping):
    	    return isBlepping
    	case .boop(let count):
    	    return count
    	case .pet(let location):
    	    return location
    	case .leave(snack: let snack):
    	    return snack
    	}
	}
}

One thing that’s going to become a problem is that it’s going to be really hard to send a tuple as a payload. So, we’re going to eventually have to wrap this in a struct, which is a tragic limitation of my system here.

Now in our notifier, we’ll post some posts.

public func didTap(on spot: PugView.Spot) {
    switch spot {
    case .nose:
        pug.boopCount += 1
        PugNotification.post(.boop(count: pug.boopCount))
    case .mouth:
        pug.isBlepping = !pug.isBlepping
        PugNotification.post(.blep(isBlepping: pug.isBlepping))
    case .ear:
        PugNotification.post(.pet(location: .ear))
    case .back:
        PugNotification.post(.pet(location: .back))
    case .tummy:
        PugNotification.post(.pet(location: .tummy))
    case .tail:
        PugNotification.post(.leave(snack: SnackInfo(name: "treat", count: 2)))
    }
}

So, now we can post anything.

I will add this observer token method to wrap NS object protocol because I’m trying not to leak my notification dependency,

class ObserverToken {
	fileprivate let observer: NSObjectProtocol

	fileprivate init(observer: NSObjectProtocol) {
    	self.observer = observer
	}
}

So, I have to give you something back that you can use to cancel, but I can obscure it through my own class, very easily. When you add an observer, very similar to notification API, we need a notification. Next, we’ll have our block that takes nothing because in this case, since this is for ones without payload.

static func addObserver
    <ContentType: TypedNotificationContentType>
    (_ notification: (ContentType) -> Self,
     using block: @escaping (ContentType) -> Void)
    -> ObserverToken {
        let name = Notification.Name(rawValue: notification(ContentType()).name)
        let observer = NotificationCenter.default.addObserver(forName: name, object: nil, queue: nil) { notification in
            if let content = notification.userInfo?["content"] as? ContentType {
                block(content)
            }
        }
        return ObserverToken(observer: observer)
}

So, from the outside when you use this:

public class NotificationPrinter {
	public init() {
    	var token = PugNotification.addObserver(.arrive) {
        print("Arrive!")
    }

So, this is fundamentally identical because there are no types, there’s no payload. This is really simple, so far. What come next is where the magic happens.

static func addObserver
    <ContentType: TypedNotificationContentType>
    (_ notification: Self,
     using block: @escaping (ContentType) -> Void)
    -> ObserverToken {
        let name = Notification.Name(rawValue: notification(ContentType()).name)
        let observer = NotificationCenter.default.addObserver(forName: name, object: nil, queue: nil) {_ in
         block()
        }
        return ObserverToken(observer: observer)
}

If I want to take and I want to make it such that this takes something here, and that it’ll be typed, then I need the magic of generics. I’ll have a content type that my block will return, that way, when my is block on the outside, I will get a defined type. But how do I define this type?

You may notice right now it is just some arbitrary type, and the magic of Swift is that secretly, what type is this?

let n = PugNotification.boop

This is the key insight that I had that makes this all come together. This is of type UInt to notification because in Swift methods are magically curried onto their class or enum. So, the magic that lets all this work is that I change this to content type to self.

static func addObserver
    <ContentType: TypedNotificationContentType>
    (_ notification: (ContentType) -> Self,
     using block: @escaping (ContentType) -> Void)
    -> ObserverToken {
        let name = Notification.Name(rawValue: notification(ContentType()).name)
        let observer = NotificationCenter.default.addObserver(forName: name, object: nil, queue: nil) {_ in
          block()
        }
        return ObserverToken(observer: observer)
}

And that’s how we determine what kind of thing to put in.

So, the notification we give in will define this type, so that our things are typed.

You’ll notice that the raw value no longer works because now we don’t have a notification anymore. So, how do make a notification? We would need to provide it with this content type. We need to provide it with an instance in order to make a notification, because right now we have a function for making notifications, so we need to make one, which means that we need to define a protocol.

So, we’re going to define a protocol in order to make us be able to manufacture these payloads. Which just has an init method, so that’s pretty easy.

Now we are going to manufacture one of the contents, use it with the function we got in to create the right notification, basically, fabricating a notification for you under the hood to grab the name, and then, we’re observing that name.

You’ll notice that we are not getting nothing back because we want to observe this notification so that we can get the content out of it. So, we will do the unwrapping under the hood for us.

static func addObserver
    <ContentType: TypedNotificationContentType>
    (_ notification: (ContentType) -> Self,
     using block: @escaping (ContentType) -> Void)
    -> ObserverToken {
        let name = Notification.Name(rawValue: notification(ContentType()).name)
        let observer = NotificationCenter.default.addObserver(forName: name, object: nil, queue: nil) { notification in
            if let content = notification.userInfo?["content"] as? ContentType {
                block(content)
            }
        }
        return ObserverToken(observer: observer)
}

We know, because we posted this notification, that all content lives under the key content.

We look at the notification, we look at the user info that we know we passed in earlier, grab the content out, cast it, since we’re being safe. One of the problems with this is that if something doesn’t work, it just silently fails. So depending on your tolerance for exclamation marks, you could exclamation mark this, and have YOLO Swift.

This is the part that kind of makes this all cohere and become way Swiftier. You use this knowledge of types of methods being curried onto the class to know which type you’re getting out.

token = PugNotification.addObserver(PugNotification.boop) { count in
        print(count)
    }

One of the other shortcomings is that you have to retype the notification every time. We have guaranteed it that the type we want in and out is of type typed notification content type.

extension UInt: TypedNotificationContentType {}

It already has an init method, you may not be surprised by this. We can just say empty notification or empty extension.

If we look inside, the token above count is a UInt.

The one other piece of this that is complicated is that because we need to require this protocol conformance for this to work, we need to able to guarantee that we can create an instance of this content type. One of the limitations of this is that you cannot use a tuple as a payload because you can’t require tuples to conform to protocols. The problem with that is in PugNotification we have our little snack name and our snack count, but in reality, what we would have to do is have a struct, snack info, where we have let name, let count. You have to wrap up your payloads and structs. I guess it’s nice, because it gives you typing information but it’s probably not as convenient as just letting there be a tuple there. That’s the limitation of this until we can make specific tuples conform to protocols.

Questions (29:31)

Why did you have to wrap the observer token?

I didn’t have to. In my original implementation of this, I did just throw the NS object protocol back out. I wanted to make people who are using this API not have to import foundations. The idea is that you should just not know how this works under the covers. You just post notifications, and you get some kind of opaque token object back. It works perfectly without doing that.

The thing that I didn’t write down here was if you write some kind of function for removing observers, the notification center API is that you remove observer and you give back the observer token that you got before, so you would give back this token and then you would go down and get the observer out of it.

Which, because this file is private, I can get, but no one else can. So, that was the only reason. It was just a airtight API.

What are the benefits of using a tuple verses a struct for a payload with multiple things in it?

The way you would typically use an enum is by having a case “whatever thing for string and thing two” is the naive way you would use an enum. Actually that’s creating a tuple that is the associated value. So, if you didn’t require the protocol conformance, you could put a tuple in, and get a tuple out, then, you would subscript it by the keys. However, because of the protocol conformance, you have to make a struct, which is not that much work.

This is also the other nice thing about using a struct is that structs can conform to protocol. In some of mine, the struct conforms to expressible by string literal. You can just pass in strings and it magically works, where a tuple could never do that.

Did you file a radar?

No, I was too busy writing the code for this so I need to go home and file that radar. No one on the Internet seems to have ever run into that problem, I Googled for a while, and no one has ever found that. So, I need to make a reducible test case, I guess.

Thank you!

Next Up: Realm Swift enables you to efficiently write your iOS app’s model layer in a safe, persisted, and fast way.

General link arrow white

About the content

This content has been published here with the express permission of the author.

Michael Helmbrecht

Michael designs and builds things: apps, websites, jigsaw puzzles. He’s strongest where disciplines meet, and is excited to bring odd ideas to the table. But mostly he’s happy to exchange knowledge and ideas with people. Find him at your local meetup or ice cream shop, and trade puns.

4 design patterns for a RESTless mobile integration »

close