mikeash.com: just this guy, you know?

Posted at 2015-01-23 14:52 | RSS feed (Full text feed) | Blog Index
Next article: Friday Q&A 2015-02-06: Locks, Thread Safety, and Swift
Previous article: Friday Q&A 2014-11-07: Let's Build NSZombie
Tags: fridayqna letsbuild notifications swift
Friday Q&A 2015-01-23: Let's Build Swift Notifications
by Mike Ash  
This article is also available in Bosnian (translation by Vlada Catalic), Macedonian (translation by Vlada Catalic), Polish (translation by Natasha Singh), Urdu (translation by Samuel Badree), and Sindhi (translation by Samuel Badree).

NSNotificationCenter is a useful API that's pervasive in Apple's frameworks, and often sees a lot of use within our own code. I previously explored building NSNotificationCenter in Objective-C. Today, I want to build it in Swift, but not just another reimplementation of the same idea. Instead, I'm going to take the API and make it faster, better, stronger, and take advantage of all the nice stuff Swift has to offer us.

NSNotifications
NSNotifications are both simple and powerful, which is why they show up so often in the frameworks. Specifically, they have a few distinct advantages:

  1. Loose coupling between notification senders and receivers.
  2. Support for multiple receivers for a single notification.
  3. Support for custom data on the notification using the userInfo property.

There are some disadvantages as well:

  1. Sending and registering for notifications involves interacting with a singleton instance with no clear relationship to your classes.
  2. It's not always clear what notifications are available for a particular class.
  3. For notifications which use userInfo, it's not always clear what keys are available in the dictionary.
  4. userInfo keys are dynamically typed and require cooperation between the sender and receiver that can't be expressed in the language, and messy boxing/unboxing for non-object types.
  5. Removing a notification registration requires an explicit removal call.
  6. It's difficult to inspect which objects are registered for any given notification, which can make it hard to debug.

My goal in reimagining notifications in Swift is to remedy these problems.

API Sketch
The public face of the API is a class called ObserverSet. An ObserverSet instance holds a set of observers interested in a particular notification sent by a particular object. Rather than declaring string constants and intermediating through a singleton, the existence of a notification is just a public property of the class:

    class ExampleNotificationSender {
        public let exampleObservers = ObserverSet<Void>()

The Void is the type of data that's sent along with the notification. Void denotes a pure notification with no additional data. Sending data is as simple as providing the type:

        public let newURLObservers = ObserverSet<NSURL>()

This is a notification that provides a NSURL to all observers each time it's sent.

Multiple pieces of data are no problem with the magic of tuples:

        public let newItemObservers = ObserverSet<(String, Int)>()

This provides each observer with the name and index of a new item. If you want to make this more explicit, you can even give the parameters names:

        public let newItemObservers = ObserverSet<(name: String, index: Int)>()

To register an observer, add a callback to the observer set:

    object.exampleObservers.add{ println("Got an example notification") }
    object.newURLObservers.add{ println("Got a new URL: \($0)") }

The add method returns a token that can be used to remove the observer:

    let token = object.newItemObservers.add{ println("Got a new item named \($0) at index \($1)") }
    ...
    object.newItemObservers.remove(token)

A common case for notifications is to receive them with a method, and deregister the notification when the instance is deallocated. This is easy to accomplish using a variant add method:

    object.newItemObservers.add(self, self.dynamicType.gotNewItem)

    func gotNewItem(name: String, index: Int) {
        println("Got a new item: \(name) \(index)")
    }

The self.dynamicType syntax is a bit verbose and redundant, but tolerable. The observer set will hold a weak reference to self and automatically remove the observer when the instance is deallocated, and in the meantime it will invoke gotNewItem whenever it sends a notification.

Sending a notification involves calling notify and passing the appropriate parameters:

    exampleObservers.notify()
    newURLObservers.notify(newURL)
    newItemObservers.notify(name: newItemName, index: newItemIndex)

This makes for a really nice API. Going through the disadvantages listed above:

  1. There's no singleton involved. Each (object, notification) pair is represented with a separate observer set instance.
  2. All notifications available for a class are public properties of that class.
  3. Explicit parameters are used to pass data to observers. They can be named in code to make it clear exactly what they are.
  4. Notification parameters are statically typed. The types are specified in the observer set property and notification senders and receivers are checked by the compiler. All types are supported, with no need for boxing.
  5. For the common case where an observer is removed when deallocated, removal can be automatic.
  6. Each observer set maintains a list of entries which can be inspected in the debugger.

Looks good! How, then, do we build it?

Observer Function Types
Let's assume that the parameters to an observer function are called Parameters, which is the name I'll use for the generic type in the code. Fundamentally, an observer function's type is then Parameters -> Void. However, for the common case where the observer function is a method, this makes it difficult to hold a weak reference to the observer object and clear the entry when the object is destroyed. When you get a method from a class, like self.dynamicType.gotNewItem, the type of the resulting function is actually TheClass -> Parameters -> Void. You call the function and pass it an instance of the class, and it then returns a new function for the method that applies to that instance.

In order to keep everything organized, we'll store a weak reference to the observer object, and we'll store the observer function in this longer form. For the simpler form of the add method, the function can simply be wrapped in another function that throws away its parameter and returns the original function. Since observer objects can be different types, we'll store them as AnyObject and the observer functions as AnyObject -> Parameters -> Void.

Code
It's time to look at the implementation. As usual, the code is available on GitHub:

https://github.com/mikeash/SwiftObserverSet

Entries
Each entry in an observer set is a weakly held observer object and a function. This small generic class bundles the two together, allowing for arbitrary parameter types:

    public class ObserverSetEntry<Parameters> {
        private weak var object: AnyObject?
        private let f: AnyObject -> Parameters -> Void

        private init(object: AnyObject, f: AnyObject -> Parameters -> Void) {
            self.object = object
            self.f = f
        }
    }

The observer set can use the object and function to call observers, and can check the object for nil to remove entries for deallocated objects. This class is marked public because it will also be returned to callers to be used as the parameter to the remove method.

Ideally, this class would be nested inside ObserverSet. However, Swift doesn't allow nesting generic types, so it has to be a separate top-level type.

Observer Set
The ObserverSet class also has generic Parameters:

    public class ObserverSet<Parameters> {

NSNotificationCenter is thread safe, and this class should be as well. I chose to use a serial dispatch queue to accomplish that:

        private var queue = dispatch_queue_create("com.mikeash.ObserverSet", nil)

I also wrote a quick helper function for it:

        private func synchronized(f: Void -> Void) {
            dispatch_sync(queue, f)
        }

With this, it's as simple as writing synchronized{ ...code... } on the code that uses shared data.

The entries are kept in an array:

        private var entries: [ObserverSetEntry<Parameters>] = []

Properly speaking, this should be a set rather than an array. However, sets aren't all that nice to use in Swift yet, as there's no built-in set type. Instead, you have to either use NSSet, or use a Dictionary with a Void value type. Since observer sets will typically contain a few entries at most, I decided to go for clarity instead.

Swift also insists on an explicit public initializer, even though it's empty, as the default one apparently isn't made public:

        public init() {}

That takes care of setup. Let's look at the main add method, which takes an observer object and function:

        public func add<T: AnyObject>(object: T, _ f: T -> Parameters -> Void) -> ObserverSetEntry<Parameters> {

Next, construct an entry. The type of f doesn't quite match what ObserverSetEntry expects, as it's looking for a function that takes AnyObject, while this one takes T. A small adapter takes care of the mismatch with a type cast:

            let entry = ObserverSetEntry<Parameters>(object: object, f: { f($0 as T) })

It's unfortunate to bypass Swift's type system in this way, but it gets the job done.

With the entry created, add it to the array:

            synchronized {
                self.entries.append(entry)
            }

Finally, the entry is returned to the caller:

            return entry
        }

The other add method is a small adapter:

        public func add(f: Parameters -> Void) -> ObserverSetEntry<Parameters> {
            return self.add(self, { ignored in f })
        }

This passes self as the object simply because it's a convenient pointer that's guaranteed to stay alive. Since entries with nil objects are removed, this keeps the entry around as long as the observer set itself. The function passed in for the second parameter just ignores its parameter and returns f.

The remove method is implemented by filtering the array to remove the matching entry. Swift's Array type doesn't have a remove method, but the filter method accomplishes the same task:

        public func remove(entry: ObserverSetEntry<Parameters>) {
            synchronized {
                self.entries = self.entries.filter{ $0 !== entry }
            }
        }

The notify method is simple in principle: for each entry, call the observer function, and remove any entry with a nil observer object. However, it's made a bit complicated by the fact that entries needs to be accessed from within synchronized, but it's a bad idea to call observer functions from there, because it could easily lead to a deadlock. To avoid that problem, the strategy is to collect all of the observer functions in a local array, then iterate and call them outside of the synchronized block. Here's the function:

        public func notify(parameters: Parameters) {

The functions to call are collected in an array:

            var toCall: [Parameters -> Void] = []

Note that the type of the functions doesn't include the initial AnyObject ->. To keep things simple, we'll make that first call within the synchronized block, so that the functions collected in the array are the final observer functions with the observer object already applied.

Iterating over the entries needs to happen in a synchronized block:

            synchronized {
                for entry in self.entries {

Skip entries with a nil object:

                    if let object: AnyObject = entry.object {

Calling entry.f with the object produces the observer function to call:

                        toCall.append(entry.f(object))
                    }
                }

Before we leave the synchronized block, clean up the entries by filtering out the ones that now contain nil:

                self.entries = self.entries.filter{ $0.object != nil }
            }

Now that the functions are collected and the synchronized block is finished, we call the observer functions:

            for f in toCall {
                f(parameters)
            }
        }

That's it for the ObserverSet class. Let's not forget the closing brace:

    }

A Note on Tuples
You'll note that none of the ObserverSet code presented addresses the case where there are multiple Parameters, for example:

        public let newItemObservers = ObserverSet<(String, Int)>()

However, it works anyway, using the code above. What's going on?

It turns out that Swift makes no distinction between a function that takes multiple parameters and a function that takes one paremeter whose type is a tuple. For example, this code calls a function using both ways:

    func f(x: Int, y: Int) {}

    f(0, 0)

    let params = (0, 0)
    f(params)

This means that the ObserverSet code can be written for functions with one parameter, and it gets support for multiple parameters for free. There are some strong limits on what you can do in these cases (for example, it's essentially impossible to modify any of the parameters) but it works great in this case.

Conclusion
NSNotificationCenter is handy, but Swift language features allow for a much improved version. Generics allow for a simple API that still allows for all the same use cases while providing static types and type checking on both sides.

That's it for today! Come back next time for more frightening experiments. In the meantime, Friday Q&A is driven by reader ideas, so if you have something you'd like to see covered here, send it in!

Did you enjoy this article? I'm selling whole books full of them! Volumes II and III are now out! They're available as ePub, PDF, print, and on iBooks and Kindle. Click here for more information.

Comments:

I developed something similar for my code (called it Channels). It has somewhat simpler implementation (storing direct functions in the arrays and not curried method functions like what you create.

Of course, in this case I can't unsubscribe to the channels but so far it works for my use-cases.

Regarding your concept of this replacing the NSNotificationCenter notifications, there is one important use-case that not covered in this approach, and seems not possible indeed, is observing a notification on any object that sends it (nil for object in addObserver). In a way this is also what allows such loose coupling on NSNotifications.

A question that I do have, is why do you have to go through dynamicType property? You could use ClassName.method, if I'm not mistaken, too.
I'm not sure if I've ever seen a legitimate use of a nil object. The catch-all facilities of NSNotificationCenter are interesting, but are hard to use without making dangerous assumptions about the global structure of the process you're living in.

As for dynamicType, you can certainly use the class name instead. I decided not to simply because that ends up being repetitive and requires renaming a bunch of extra spots if you change the class name. Not a particularly big deal, more a matter of personal preference.
This isn't really the same as using NSNotifications/Center. NSNotifications can be anonymous and these are not, so you're missing the biggest difference between notifications and observers. This is an interesting implementation of observers in Swift but it's clearly not a replacement for NSNotificationCenter.
In fact, I can't think of an instance where I've seen or used NSNotificationCenter for anything other than anonymous notification broadcast and reception. If I knew the object I wanted to watch I would just use an observer.
How do you use nil-targeted notifications without effectively turning those parts of your application into singletons?
Why would that effectively make them singletons? Objects register and deregister for various notifications as they come and go. At least when using NSNotificationCenter, registering for events that are never broadcast is just fine. For non singleton objects I mostly use NSNotificationCenter for system events. But since I don't consider singletons an inherently bad thing, I also use them as communication mechanism for the few singleton objects I usually have. Stuff like logout and login events to tell various singletons to do something (i.e. clear caches, reset networking, write to disk, delete files). NSNotificationCenter also serves as a central sort of service for notifications, so I don't have to know exactly what objects I want to see certain events for in the first place.

Also, if you're redoing the observer/notification mechanism anyway, why not tackle the most annoying aspect of using them, manually deregistering them. I've seen solutions for that in Objective-C but a Swift implementation would be interesting, if possible.
No legitimate use for catch-all?

I use that all the time. E.g. when managing a collection of some objects that all post notifications you are interested in and that collection changes over time.

With catch all you register a single notification to watch for posts from any object in [init], then in your handler method:

if ([collection containsObject:note.object]) {
handle notification...
}

and in [dealloc] you deregister

This would messier and possibly more bug prone without catch-all -- even with the Swift version.
Jon: If you register for a certain notification sent by all objects, now your code has to be aware of all objects in your app that send that notification, even if they're being managed by a completely different instance. For your logout event example, you wouldn't want to clear caches or delete files if some other instance's session logged out, so you're effectively limiting yourself to one session at a time. I think it just comes down to your statement that "I don't consider singletons an inherently bad thing." If you're comfortable limiting things to one instance even when it's not strictly required then that concern goes away, but I'm not, myself.

I'm a bit puzzled about your "manually deregistering" comment, since the code above does handle exactly that with no fuss.

Brian B.: Adding a notification observation to each object as you add it to the array, and removing it when you remove from the array, seems just as easy and no more bug-prone. Unless you add and remove from many different places, which you probably shouldn't be doing anyway.
Although deallocated observers are no longer called, doesn't this have the problem of still leaving the ObserverSetEntry object (with nil object) in the observer set forever? So if you regularly register observers, and they deallocate, the array will steadily build up with junk.
Stuff will build up if you never send notifications, since dead entries are only cleaned when notifying. That would easily be remedied with a check when adding as well, if needed.
I'm not sure why you're saying I have to be aware of all objects that send a notification (if you mean I need a reference), since that's exactly the scenario I avoid when using anonymous notifications. Some object posts a "Logout" notification, I don't really care what it is. Some other object or objects receive that notification and take some action. Or perhaps no objects have yet been created that listen for that notification. At most I have to be aware of what notifications could be sent from a class, if I care, and that's just a documentation issue. I can't be bitten by notifications I don't know about.

Or did you mean that multiple classes could send the same notification for different reasons and I might not know about it? i.e. Some random object could broadcast a notification I listen for I wasn't expecting. That just seems like bad notification usage by the offending object. Apple's classes don't do that (notifications are unique to a class or framework, usually). If a third party library was, say, issuing the UIKeyboard notifications for some reason, I'd probably rethink using that framework, if I could.

If there are really times that I don't want to take action based on a notification, I rely on the info dictionary to determine what action to take. In my experience, such situations are rare, especially for notifications that are intended to be global. If the notification doesn't include a useful info dict, again, that seems like bad notification design, not a problem with the anonymous notification pattern in general.

My only point is that your solution, while interesting, completely ignores this valid and useful scenario for notifications.
I agree with Jon. NSNotificationCenter is useful, partly, because it does allow anonymous notifications. I believe that's a big part of what it was invented to do!

The idea that you could somehow end up with multiple instances that would all process a logout event, and clobber each other's efforts, seems like a stretch. People who want to use anonymous notifications are likely to be mindful of what they're doing, and it's wrong to suggest that they should feel insecure for correctly employing bona fide Cocoa features.

From a blog called NSBlog, I would have expected a more humble and encouraging approach. Attempting to position oneself as superior to the creators of Cocoa is asinine.
Jon: What I'm saying is that there are potentially a lot of instances in your program that could be sending a notification, and the appropriate action for an observer depends on which instance it is. For your logout notification example, again, you might want to delete credentials, but *only* when that's sent by the login controller you actually care about. Another login controller used by a different part of the program should not cause that to happen. It sounds to me like you're relying way too much on there only being one instance of any given class.

David: If you expected a humble approach here then I think you may be confused. You'll get zero traction with me by proposing that I shouldn't be critical of what I see as Cocoa design mistakes. Cocoa has suffered from some mistakes during its long life, and I'm not going to be shy about pointing them out. More than that, a lot of Cocoa programmers value expediency over good design. For example, I see a ton of Cocoa apps that are chock full of singleton objects that shouldn't be there, and are used just because it's slightly easier to access a singleton than to pass instances around. Anonymous notifications look to be the same sort of problem to me. You certainly have every right to disagree, but you need to bring some sort of argument if you want to discuss it, not simply declare me to be wrong because I might disagree with Apple.
Keep writing, though! Sorry, I didn't mean to be discouraging myself. Big fan, just disappointed by the attitude.
Nicely done. Suggestion: use "_" instead of "ignored".

Jon: I agree with Mike, and Apple's classes definitely do this. It's like listening to UITableViewSelectionDidChangeNotification on <em>all UITableViews</em> in the app. You can do it, but it only makes sense if you know that all the UITableViews in your app should behave the same way. The same goes for any notifications you create yourself: if everything in the app deserves the same response, then it's great...but it makes it harder if you ever want to differentiate in the future.
Why do you get a reference to self.entries in description() and then continue using self.entries? If that is not a typo, can you explain the reasoning behind it.
One of the legitimate uses of nil-objects could be listening to NSManagedObjectContextDidSaveNotification.
App could have multiple contexts for internal needs and main thread could be integrating changes of data into main context. And main context should not be aware of all possible background contexts. So, we listen to all notifications and merge those changes.
Great post! A question for you:

You mention how it's unfortunate to bypass swifts type safety when you go from AnyObject to T when adding an entry. Why must you use AnyObject in one place and T : AnyObject in the other? Can you please explain the reasoning behind that? (Sorry if this is obvious, I'm a swift beginner).

Also, another reason to avoid anonymous notifications: testing becomes a huge pain since there may be multiple instances of those objects you thought were singletons while all those tests are running. I guess that's also a reason to avoid singletons.
A useful use case for anonymous notifications is tracking bundles dynamically loaded by registering for NSBundleDidLoadNotification.
David: No worries, I can handle it. Sometimes I have strong beliefs and I'm not shy about telling people. I don't expect everybody to agree though.

Jordan: Nice suggestion about using _ instead of ignored. I think I'm going to stick with "ignored" for now as it seems more clear to me. This may just be due to the newness of the language, though. Once people have used Swift for a while I imagine that _ will look more natural there.

Pavel: Temporary brain malfunction. I pushed a quick fix. Thanks for pointing that out!

Deniska: Doesn't that assume that your app is only ever manipulating one Core Data store? That seems like a bad assumption to make.

Jason: Excellent question. It comes down to not being able to figure out a good way to have a heterogeneous collection with generics. In isolation, it would make sense for ObserverSetEntry to be written as ObserverSetEntry<T, Parameters>. That way, everything would be consistently genericized and life would be good. But I couldn't figure out a way to have ObserverSet reasonably contain and talk to a bunch of ObserverSetEntry objects with different T types. By having them all use AnyObject that problem is bypassed.

Jean-Daniel: I agree, that looks like a good case for nil. In this particular case, since you'd likely never want to listen for that notification for anything but nil, the same functionality could be provided with a global ObserverSet instance for all of NSBundle. Loaded code is inherently global, of course, and it would make sense to have an explicitly global notification system to go with it.
I use the same Core Data pattern that Deniska describes, to very good effect.

The first thing in the NSManagedObjectContextDidSaveNotification handler method is a test of the posting context to see if it's one I care about: "Is this save affecting my store?"

Given the extensive creation of scratch contexts all through the app, it's simpler than registering a listener for each context as I create it, and deregistering as I discard.
Mikeash I still don't get why you get a local reference to self.entries in description, is that supposed to make a copy instead of a reference?
This is a really nice API!

Can you explain what you mean by "(for example, it's essentially impossible to modify any of the parameters)" in the section about using named tuples as the argument to a single-parameter function?
Pavel: It does make a copy. Swift arrays have value semantics, so just doing a = b gets you a new one.

Bill: Consider a generic passthrough function:

func call<T>(function: T -> Void, parameters: T) {
    function(parameters)
}


This works for multiple parameters. Now imagine if you wanted to write a variant that would, say, add 1 any integer parameters before calling the original function. There's no reasonable way to accomplish that.
Hi Mike - thanks for another informative article! I've always found your articles very instructive, particularly the ones on the low-level performance of Swift.

I completely agree with you on the evils of singletons, of which nil uses of NSNotificationCenter are certainly one. It might be possible to argue that if we are using a non-shared instance of the notification centre (ie one that we instantiated and injected everywhere it is used to subscribe / publish) then it is no longer a singleton but merely a stateful object with dynamic properties. However, almost all uses of it seem to pass through the shared instance.

Unfortunately there are several scenarios where it seems that we have no choice but to use this singleton behaviour, such as when listening for keyboard notifications (in iOS) because the objects we want to listen to are not made available to us (if anyone knows a way around this I would be interested to hear it).

I have been taking great pains to get rid of all singletons in my code (to the length of injecting NSBundle and UIScreen into objects which need access to them, instead of calling mainBundle and mainScreen). Might I suggest an article on the best ways to avoid singletons in Cocoa?

For those interested, the most persuasive argument against singletons that I came across is this old article - http://www.object-oriented-security.org/lets-argue/singletons
It seems that so many code/application security problems would simply not exist if complete dependency injection were enforced.

Thanks again.
Very interesting article, thanks.
Regarding the use of dispatch_sync to a serial queue, if you've ever benchmarked that particular construct you'll know that it is frightfully slow. You could instead use OSSpinLock, which would be just fine for the particular application you're using:


private var lock = OS_SPINLOCK_INIT
private func synchronized(f: Void -> Void) {
  OSSpinLockLock(&lock)
  f()
  OSSpinLockUnlock(&lock)
}


The time delay involved can be orders of magnitude shorter this way than when using dispatch_sync.
Mike,

Actually, that approach works okay with multiple core data stores configuration.
Notification has sender which is a context being saved. I check that my context and sender have the same persistentStoreCoordinator.
If not - I don't need to merge changes.
Something like that.
And moreover, this works faster (in my case) than having several nested contexts.
Guillaume Lessard: Coincidentally I discuss the pros and cons of that in my latest article. You're right that a spinlock would work pretty well here, but I prefer not to use them without some identified speed problem.

Deniska: I'd say that indicates a design problem in the Core Data API. If save notifications affect everything related to a particular persistent store coordinator, then that should be the object posting the notifications, not the individual contexts. (If there are cases where you only care about the specific context then I'd say both should be sending a notification.)
Can you please put some light on this syntax:

private let f: AnyObject -> Parameters -> Void
That type is a function that takes an AnyObject and returns another function. The return type takes Parameters and returns Void
Although I am not quite familiar with Swift(I still write code in OC), the explanation in the article is really throughout. Maybe it's the time to switch to Swift

Comments RSS feed for this page

Add your thoughts, post a comment:

Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.

Name:
The Answer to the Ultimate Question of Life, the Universe, and Everything?
Comment:
Formatting: <i> <b> <blockquote> <code>.
NOTE: Due to an increase in spam, URLs are forbidden! Please provide search terms or fragment your URLs so they don't look like URLs.
Code syntax highlighting thanks to Pygments.
Hosted at DigitalOcean.