iOS: How to notify parts of app about changes and send data in loosely coupled way
Tags: ios, swift
Published: 2020-02-15 10:28:54

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:

```swift
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:

```swift
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:

```swift
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:

```swift
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.

```swift
@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:

```swift
@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:

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

And then modify our method signature to have `Notification` parameter:

```swift
@objc func onOrderingPreferenceChanged(notification: Notification) {        
} 
```

Don’t forget to change the `addObserver`:

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

And access the sent data:

```swift
@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!_