iOS: How to notify parts of app about changes and send data in loosely coupled way

nemecek_f

Filip Němeček

Posted on February 15, 2020

iOS: How to notify parts of app about changes and send data in loosely coupled way

Let’s say you have somewhat bigger app and need to update its after user changes settings. Maybe they selected new ordering for list items, changed language or what not. Or maybe you need to display notifications badge on multiple screens.

Some of these challenges could be solved with delegates or closures. But you would need to properly connect all these parts and the result would be tightly coupled. Your list controller would need to know about settings controller and vice versa to be notified about changes.

Fortunately iOS provides pretty neat solution called NotificationCenter. This has nothing to do with push notifications or any other kind of notifications the user might see. Instead it is a way to propagate changes and send data across your application.

When you need to originate change you will use NotificationCenter to post a notification and that is the first part done. The second part is to set observers for specific notifications when you need to know about something happening.

Basic NotificationCenter usage

Imagine we want to change list (or rather TableView) ordering on multiple screens after user changes preference in settings screen.

In the settings controller, we would have method like this:

func saveOrderingPreference(new preference: String) {
        UserDefaults.standard.set(preference, forKey: "orderingPreference")

        NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "orderingPreferenceChanged")))
    }

In real code we would have enum for preferences and constant for UserDefaults key but this is an example :-)

The second line will use default NotificationCenter to post notification. It does not care whether anyone is listening.

Extension for notification constants

You may spot the issue with the Notification object construction which is not that pretty. Fortunately we can create extension to provide constants for notifications:

extension Notification.Name {
    static let orderingPreferenceChanged = Notification.Name("orderingPreferenceChanged")
}  

I recommend using fairly long and descriptive names for notifications so they don’t have chance to clash.

Now the post method call looks much better:

NotificationCenter.default.post(name: .orderingPreferenceChanged, object: nil)

Another advantage is that with constant we don’t have to worry about misspelling the name.

Now let’s subscribe to our new notification:

NotificationCenter.default.addObserver(self, selector: #selector(onOrderingPreferenceChanged), name: .orderingPreferenceChanged, object: nil)

This is typically done in viewDidLoad in case of a view controller. You have the name of the notification and #selector to mark method to execute when notification is received.

@objc func onOrderingPreferenceChanged() {
        // reload data
}

And this is basic example finished. However there are few things you should know about NotificationCenter.

Few things to note

The method selected in observer may not be executed on the main thread which will mean problems for UI related stuff like tableView.reloadData(). It is best to always use DispatchQueue.main.async when manipulating UI like so:

@objc func onOrderingPreferenceChanged() {
        DispatchQueue.main.async {
            // reload data
        }
}

Correctly handling incoming notification should be the receiver’s responsibility so posting notifications with DispatchQueue is not a solution.

Also NotificationCenter work synchronously, meaning all observers have to finish work for you code after post call to continue.

You don’t have to use NotificationCenter.default, you can create your own. This can be useful during testing or when you want to have absolute certainty that no other code (not even OS) is sending and receiving notifications.

How to send data with NotificationCenter

Notification center uses the userInfo design pattern which you can leverage to send simple data with your notification. This could be id of item to act upon or any other primitive type.

We would add userInfo to post like this:

NotificationCenter.default.post(name: .orderingPreferenceChanged, object: nil, userInfo: ["preference”: new])

And then modify our method signature to have Notification parameter:

@objc func onOrderingPreferenceChanged(notification: Notification) {        
} 

Don’t forget to change the addObserver:

NotificationCenter.default.addObserver(self, selector: #selector(onOrderingPreferenceChanged(notification:)), name: .orderingPreferenceChanged, object: nil)

And access the sent data:

@objc func onOrderingPreferenceChanged(notification: Notification) {
        if let userInfo = notification.userInfo, let preference = userInfo[preference] as? String {

        }
}

Thanks for reading!

Is anything not clear? Do you want more information? Ask in the comments and I will do my best to help you. Thanks for reading!

💖 💪 🙅 🚩
nemecek_f
Filip Němeček

Posted on February 15, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related