How To: Using Notification Center In Swift
With NotificationCenter you can broadcast data from one part of your app to another. It uses the Observer pattern to inform registered observers when a notification comes in, using a central dispatcher called Notification Center.
In this tutorial we’ll find out how the notification center mechanism works, and how you can use it in your apps. As you’ll see, Notification Center is particularly useful for getting data from one part of your app to another.
Before we get started, some quick notes:
- The
NotificationCenter
class used to be calledNSNotificationCenter
– with the “NS” prefix – prior to Swift 3. You’ll often find people still referring to it as NSNotificationCenter. - The
NotificationCenter
mechanism has nothing to do with push notifications, local notifications or the Notification Center UI, an overview of app notifications as found in iOS and macOS. - This guide solely focuses on using
NotificationCenter
to send in-app broadcasts with Swift.
Let’s continue.
- How Notification Center Works
- How To Register Notification Observers
- How To Post Notifications
- NotificationCenter In Practical iOS Development
- Further Reading
How Notification Center Works
We’ll start with how NotificationCenter works exactly. It has 3 components:
- A “listener” that listens for notifications, called an observer
- A “sender” that sends notifications when something happens
- The notification center itself, that keeps track of observers and notifications
The mechanism for Notification Center is simple. Let’s look at an analogy:
- You’re working in a mail processing facility that delivers mail to 5 color-coded streets
- You, the postman, need to deliver purple letters to purple street whenever they arrive at the mail processing facility
- When your shift starts, you tell the central command of the mail facility – let’s call him Bob – that you want to know when purple mail arrives
- Some time passes, and Alice – who lives on yellow street – decides to send a letter to purple street
- So, she sends her letter to the mail processing facility, where it is picked up by Bob – the central command – who informs you that a purple letter has arrived
- You get the purple letter from Bob, and safely deliver it on purple street
The cool thing about this mechanism is that Bob and Alice don’t need to know each other, they only need to “know” the central command – the NotificationCenter
.
Also, the Notification Center can handle one-to-many and many-to-many messages. Alice can send stuff to Bob, Arthur can send stuff to Bob, and if Alice is listening in, Bob and Arthur can also send stuff to Alice!
Back to the mechanism. Here’s an overview of the flow of data for NotificationCenter:
This is what happens:
- Component B tells NotificationCenter that it wants to observe a notification with
addObserver(_:selector:name:object:)
- Something happens in Component A, like data coming in or a task that completes
- Component A creates a notification and posts it to the Notification Center
- The Notification Center notifies Component B that a notification it observes has been posted by calling a selector on Component B
- The selector is called, effectively executing a function of Component B
Component A and B can be any kind of class, such as a view controller, a custom API class or a data model.
Makes sense? Let’s move on!
How To Register Notification Observers
Here’s how you register an observer:
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveData(_:)), name: .didReceiveData, object: nil)
This adds an observer with those parameters to the Notification Center, essentially telling it that self
wants to observe for notifications with name .didReceiveData
, and when that notification occurs, the function onDidReceiveData(_:)
should be called.
The addObserver(_:selector:name:object:)
function has 4 parameters:
- The first parameter
observer
is unnamed, and you provide it with a reference to the object that is the observer for this notification, which often isself
- The second parameter
selector
is the function you want to call when the notification occurs, which works like the target-action pattern - The third parameter
name
is the name of the notification you want to listen for, which is of typeNotification.Name
- The fourth parameter
object
is an optional object whose notifications you want to receive, so if you set it, you’ll only receive notifications from that “sender”
It’s smart to think of the observer
and the selector
as the target and the action. What function, a so-called selector, do you want to call on what object, a so-called observer, when the notification occurs?
Note: In Objective-C, a selector is a type that refers to the name of an Objective-C method. In Swift, we just call it a function – although there’s difference. You define the selector by typing #selector()
, with the function name written within the parentheses.
It’s important to use the correct selector here, including the right function parameters, so it’s easiest to use Xcode‘s code auto-completion by pressing the Esc key after placing the cursor in between the parentheses. Try it!
The function you want to call when a notification is observed, can be anything. In the example above, it could look like this:
@objc func onDidReceiveData(_ notification: Notification) {
// Do something now
}
The function has one parameter, which is a Notification
object. You’ll see later on that you can use property userInfo
to send some data with the notification.
Note: You need to mark this function with . That attribute makes your Swift function available in the Objective-C runtime. The
NotificationCenter
is part of the Objective-C runtime, so in order for it to call your function, you’ll need to add that attribute.
Every notification has a name of type Notification.Name
. You can create it like this:
let name = Notification.Name("didReceiveData")
It’s more convenient to add your notification names as static constants to an extension for Notification.Name
, like this:
extension Notification.Name {
static let didReceiveData = Notification.Name("didReceiveData")
static let didCompleteTask = Notification.Name("didCompleteTask")
static let completedLengthyDownload = Notification.Name("completedLengthyDownload")
}
The notification names are now available as static constants on the Notification.Name
type, so you can use this syntax:
NotificationCenter.default.addObserver(self, selector: selector, name: .didCompleteTask, object: nil)
Note: I often use the “did do something” or “has downloaded something” naming convention for notifications, and I’m naming notification selectors similarly with onDidSomething(_:)
.
A key part of using NotificationCenter
is that you register an observer before the notification you want to observe occurs. When a notification is posted to the notification center, and you’re not observing yet, you will obviously miss the notification.
By the way, the default
property of the NotificationCenter
is, in fact, the default notification center (duh!). All system notifications, such as keyboardDidHideNotification
, are posted there.
You can also create custom notification centers, although you probably don’t need to. When you need to filter notifications, use the notification name and sender object to determine from what component you want to receive notifications.
In view controllers, it’s common to register and remove observers in either:
-
viewDidLoad()
anddeinit
, or -
viewWillAppear(_:)
andviewWillDisappear(_:)
You can remove an observer with removeObserver(_:name:object:)
and using the exact same parameters as for addObserver(...)
. You can also remove all observers with removeObserver(_:)
. Especially the removeObserver(_:)
function is useful for quickly removing an observer from all notifications.
One last thing… Observers need to be removed before they are deallocated, or you risk sending a message to an object that doesn’t exist!
How To Post Notifications
Now that we’re observing for a notification, let’s post it! Posting a notification is simple:
NotificationCenter.default.post(name: .didReceiveData, object: nil)
The function post(name:object:userInfo:)
has 3 parameters:
- The first parameter is
name
, the notification name, of typeNotification.Name
. It’s exactly the same as the notification you’re observing, and it’s smart to use that extension here too. - The second parameter is
object
, the sender of the notification. You can leave it empty withnil
or use any object. It’s purpose is the same as theobject
parameter ofaddObserver(...)
, so you can use it to filter the sender of a notification. - The third parameter is
userInfo
, and you can use it to send extra data with the notification. The parameter is not required, so you can leave it out if you don’t need it.
Let’s say you want to display some data in a table view. The data comes from a JSON webservice, and contains the game scores of Bob, Alice and Arthur.
You register for the .didReceiveData
notification in the table view controller, like this:
NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveData(_:)), name: .didReceiveData, object: API.shared)
And the onDidReceiveData(_:)
function looks like this:
@objc func onDidReceiveData(_ notification: Notification)
{
if let data = notification.userInfo as? [String: Int] {
for (name, score) in data {
print("\(name) scored \(score) points!")
}
}
}
In the example above, this happens:
- First, you’re using conditional binding to downcast the type of
userInfo
to[String: Int]
with the type cast operatoras?
. Because the type ofuserInfo
is[AnyHashable: Any]?
– basically any dictionary – you can downcast its type to the dictionary type you’re expecting. That’s string keys and integer values in the example above. If the cast succeeds, thendata
contains the value ofuserInfo
as type[String: Int]
. - Then, using a for in loop, the items in the
data
dictionary are iterated. We’re using a tuple(name, score)
to decompose the key and value of the dictionary items. - Finally, inside the loop, we’re printing out the names and scores of Bob, Alice and Arthur.
When we want to post the notification that contains the data, we do this:
let scores = ["Bob": 5, "Alice": 3, "Arthur": 42]
NotificationCenter.default.post(name: .didReceiveData, object: self, userInfo: scores)
See how the scores
dictionary is provided as the argument for userInfo
? We’re basically sending those score data alongside the notification. They’ll get picked up in onDidReceiveData(_:)
and printed out.
Another thing stands out in the example above: the usage of object
. When registering the observer, you’re specifying the sender you want to receive notifications from. Like this:
addObserver(···, selector: ···, name: ···, object: API.shared)
Then, when posting the notification we specify the object
parameter too. Like this:
.post(name: ···, object: self, userInfo: ···)
You already know that self
refers to the current instance of the class we’re in. So when API.shared
references the same object as self
, the notification is picked up by the observer.
Here’s what that class could look like:
class API
{
static let shared = API()
func getData() {
NotificationCenter.default.post(name: .didReceiveData, object: self, userInfo: ···)
}
}
And that’s how we post notifications! Let’s continue with some use cases…
NotificationCenter In Practical iOS Development
Let’s start by pointing out when you shouldn’t use NotificationCenter
.
The notification center mechanism is so simple and convenient, that it’s tempting to let any and all objects, view controllers and models communicate with it.
Say you have a View Controller A and a View Controller B. You want to pass some data from A to B, when B is shown on screen. Do you use the notification center? No!
It’s much simpler and clearer to use a function or property on View Controller B, like setScores(_:)
or currentUser
, than it is to use Notification Center. You might even want to use a closure, or a set a property in a segue, or just use any of the standard principles of MVC.
Keep in mind to loosely couple your view controllers and/or objects, for instance by using a protocol or generic type.
Want to know how to pass data between view controllers, in any scenario? Check out the extensive blog post I wrote on that topic.
So when do you use NotificationCenter
? I use the following guidelines:
- When communication between two or more components of your app, that have no formal connection, needs to happen
- When communication needs to happen repeatedly and consistently
- When using one-to-many or many-to-many communication
Imagine you have a table view controller and an API. The data from the API needs to end up in the table view controller, after it has been downloaded from a web-based JSON resource. Both classes have single responsibilities, respectively getting the data and showing the data.
It doesn’t make sense to connect the table view controller to the API directly. You’re not going to add a tableView
property to the API
class, and add data to its data source when the JSON has been downloaded.
Likewise, it doesn’t make sense to add a dataCollection
property to the API
class and let the table view controller use that directly. You likely want to use a separate datastore anyway.
No, you want to loosely couple the table view controller and the API, for two reasons:
- So you can use the data from the API for something else, like search, or another view controller with a different layout
- So you can use the table view controller for something else, like displaying search results or showing other different but similarly looking data
Because of that, you end up using NotificationCenter
. This has a few added benefits:
- When another part of your app also needs to respond to new data coming in, it can simply listen in for the same notification name. This is one-to-many communication, because the one API can post notifications to many observers.
- When that same table view controller is used to display different data, for instance for a search feature, it can observe a different notification name or distinguish notifications by their sender. This is many-to-one or many-to-many communication, because now two view controllers are listening in on data from different sources.
Finally, I think that the determining guideline for using Notification Center should be repeat and consistent use. That connection between the table view controller and the API is like an information superhighway, moving data from one part of your app to the next.
Every time the user “pulls to refresh”, initiates a search, or needs new data, a signal is sent to the API to download new data, after which a notification is posted. Each of the components that observes that data now gets a ping, and the UI is subsequently updated in the app. Neat!
Further Reading
So! Notification Center. Now you know… Here’s a quick summary:
- First, register an observer for a notification with:
addObserver(_:selector:name:object:)
- Then, post a notification with
post(name:object:userInfo:)
… - … after which your selector is called
- And don’t forget to remove the observer with
removeObserver(···)
You use NotificationCenter
for repeat, consistent one-to-many or many-to-many communications. If you’re not sure, check out how to pass data between view controllers. And you can find a simple example in Xcode here.
Want to learn more? Check out these resources:
- How To: Build A Real-Time Chat App With Firebase And Swift
- How To Use Apple’s Developer Documentation For Fun And Profit
- Why App Architecture Matters
- How To Use Swift Optionals: The Ultimate Guide
- Any And AnyObject Explained In Swift
- View Controllers for iOS & Swift Explained
- Target-Action Explained in Swift