Implementing a Custom NotificationCenter Efficiently in iOS

Various ways to use the Observer class

Pedro Alvarez
Better Programming

--

Image from https://www.pexels.com/search/beach%20background/

If you work with object-oriented programming, certainly you are familiar with (or at least have already heard about) the Observer design pattern.

This pattern consists of an Observable class that emits events with custom data attached to it. Other classes that subscribe to it may get a notification when a specific event is triggered.

This pattern is important to the iOS environment and is used across APIs, such as NSNotificationCenter ,Combine and RxSwift . The observable class emits events that may be triggered internally or externally. To listen to the changes, the Observer classes should register to receive notifications.

OK, But We Already Have Delegate and Closures for That

I understand that relying on a complex pattern like Observer may be “killing a bee with a cannon.” For example, you may send an update from a ViewModel to the ViewController via NotificationCenter. But the core advantage of this is creating a class that broadcasts an event to any other class interested in that event without knowing who may be notified.

Let's say we have a ViewController with a UIButton. When this button is clicked, we want several places of the application to be notified and update the UI by showing a banner or something else. You could also notify multiple classes about a network event too. It doesn't matter how it’s handled, but it’s just like Netflix — we broadcast notifications about a new series without knowing who will/how many will receive them.

That's the idea of Observers. A few or millions may need to be notified about a single class’ event.

class (sends event and data) to notification center which notifies three observers
image by author

But what is the magic behind that? How do we notify many observers without a delay? That's what this article is for. We will implement our own custom NotificationCenter to save a collection of different yet related events and observers. This will make it so that any time our NotificationCenter receives an event with some attached data, we can broadcast that to multiple places. Let's get started.

Protocols: Observable and Observer

Two core entities in the project communicate in the same pattern. There is an Observable entity that emits events with generic attached data and allows multiple Observers to listen to events from there. Three possible actions can be delegated to the Observable:

  • notify: tells the NotificationCenter to broadcast messages with related data
  • register: tells the NotificationCenter to register a new Observer to receive notifications regarding some event
  • unregister: tells the NotificationCenter to unregister some Observer instance, so it won’t receive any other notifications regarding an event

Creating the NotificationCenter

Now that we have our protocols for establishing the relation Observable-Observer, we should implement our core class for the CustomNotificationCenter:

We implemented the NotificationCenter as a Singleton since we want a central and unique network to broadcast messages across all parts of our application. This is a classic use case for the Singleton design pattern because we don't desire multiple instances for NotificationCenter. So, we have a declared shared instance that can only be instantiated once due to the private init . We also declared all three functions in our protocol.

Saving Observers and Events

Now that we have our NotificationCenter class, we are ready to implement our data structure to save the possible events and observers that subscribe to each event. Before declaring the observers, let’s think about the best way to save this kind of data so it can be broadcast to all observers efficiently. The most standard way is to create a data structure to save an Observerand an array of strings with all the events the instance observes. Here’s what that looks like:

OK, this solution works, but it's not an efficient way of saving the observers. When receiving an event to broadcast, we should iterate through the entire observers array and check for each element if the event is registered in the data structure. In any case, we would need to iterate through the entire array (O(n)). It would take a lot of time if we had hundreds of items.

The best way to solve this complexity issue is to provide a way of accessing only the observers registered in that given event. Are there any data structures whose access operation takes O(1) time? Definitely a HashMap , or better yet, a dictionary! Now, let's reshape our observers and save a list of them, so we know all the objects subscribed to an event when triggered. Here’s the code:

Now we can get straight to the point and iterate only through the observers that subscribe to the given event.

Implementing Observable operations

Now that we have a data structure to save events and registered observers, let's implement our three core operations within our NotificationCenter:

As you can see, when we’re registering a new observer to a given event, we should check if it's not already registered to avoid duplicates. After that, we should check if the array is nil as we are dealing with a dictionary. This will save that in a key, and if it is, we instantiate the new array with the single Observer element. Otherwise, we append it to the existing array.

When we want to unregister an observer, we should only remove elements in the array for the event key that references the same memory address as the observer object. This is why we declared the concrete types for the Observer protocol as reference types.

If we want to notify all observers about an event, we should iterate through all observers in the array for the event key. After that’s done, we should send the attached data to call the receive method and notify it. This method is of type Any, so we can broadcast anything. The Observers have the task of casting the data instance with the event and checking if it's the expected one.

Testing our NotificationCenter

Now that we have implemented our NotificationCenter mechanism following the Observer design pattern, let's create three different classes: one for sending an event to the NotificationCenter, and two others to implement the Observer protocol and handle events. Here’s the code:

As you can see, we declared a class Sender that manually sends events with custom data to the NotificationCenter. We created two other classes, Observer1 and Observer2, and they registered themselves as observers to the NotificationCenter. These classes registered by creation time and implemented a receive method from the Observer protocol, expecting to receive an Event with an integer as the data. The Sender instance should trigger the event, and the observers should respond to it. Let's test what we have:

OK, here’s what we have:

As expected, that's brilliant. We receive outputs from each Observer after sending an event with the expected data format.

Conclusion

In this article, we spoke about the Observer design pattern that exists across most object-oriented programming languages. Also, we implemented a NotificationCenter that works in the same way as Apple's native one.

We presented the pros and cons of broadcasting messages with a NotificationCenter over the old Delegate and Closure mechanisms and discussed an efficient way to save events relating to observers in our NotificationCenter class.

Finally, we learned how to register, unregister, and notify them without needing to search all the items to find the registered ones.

This knowledge is required for many whiteboard challenges in US companies. I hope you are prepared for that and, of course, enjoyed the content ;).

--

--

iOS | Android Developer - WWDC19 scholarship winner- Blockchain enthusiast