
This video is only available to subscribers. Start a subscription today to get access to this and 469 other videos.
Typed Notifications
Sending Notifications with NotificationCenter
We will start with the basic usage of NotificationCenter
.
Each notification has a name you want to listen for and is of type Notification.Name
. There are two different ways we can subscribe to notification. One way is by using block we will create a strong reference to the observer calling .addObserver
and pass the name and the object whose notifications the observer wants to receive. If you pass the object as nil
, the notification center does not use a notification’s sender to decide whether to deliver it to the observer.
The second method is by using a selector-based syntax. We specify the object registered as an observer and the selector that refers to a function marked with @objc
. Along with the selector, we also pass the name of the notification for which to register the observer and the object whose notifications the observer wants to receive. The problem with the selector-based syntax is, we cannot pass this subscription if we are using types that Objective-C does not understand, such as Swift structs or classes that use generics.
To post notifications, we call post
with the notification name, the object sending the notification and a userInfo
dictionary.
To interpret the notification data we need to ensure that the userInfo
dictionary is assigned the right set of a key-value pairs. This is just a bag of data, so when you receive it on the subscribing end, you'll need to safely cast the values to the types you expect, and this is error prone and tedious.
extension Notification.Name {
static var subscriptionChanged = Notification.Name(rawValue: "subscriptionChanged")
}
class NotificationExample {
var observer: NSObjectProtocol?
func demo() {
// block
observer = NotificationCenter.default.addObserver(forName: .subscriptionChanged,
object: nil,
queue: .main) { notification in
print("Subscriptions changed")
}
// selector
NotificationCenter.default.addObserver(self, selector: #selector(onSubscriptionChanged(_:)),
name: .subscriptionChanged, object: nil)
// sending?
NotificationCenter.default.post(name: .subscriptionChanged, object: self, userInfo: [
"subscribed" : [142]
])
}
@objc
private func onSubscriptionChanged(_ notification: Notification) {
dump(notification.userInfo)
}
}
Let's create a more type-safe way of doing this, leveraging Swift's syntax and strong type safety.
Creating Protocol for TypedNotification
To have Swift-friendly notifications, we will create a protocol of TypedNotification
with the name of the notification, the type of object sending the notification. Implementers who adopt this protocol can also carry with it any data they need.
We will also have an extension for our typed notification to provide a default value for notificationName
. This way we only have to define a string when creating our own notifications.
protocol TypedNotification {
associatedtype Sender
static var name: String { get }
var sender: Sender { get }
}
extension TypedNotification {
static var notificationName: Notification.Name {
return Notification.Name(rawValue: name)
}
}
Creating Protocol for TypedNotificationCenter
We will create a protocol for TypedNotificationCenter
that will be in charge of posting these typed notifications. The addObserver
defines the type of notification, along with the sender type. We will use the block-based notification from NotificationCenter
and return an instance of NSObjectProtocol
to maintain the subscription.
protocol TypedNotificationCenter {
func post<N : TypedNotification>(_ notification: N)
func addObserver<N : TypedNotification>(_ forType: N.Type, sender: N.Sender?,
queue: OperationQueue?, using block: @escaping (N) -> Void) -> NSObjectProtocol
}
Now we will extend the NotificationCenter
to adapt and conform to the TypedNotificationCenter
.
While posting, we will pass the name of the notification, object as sender and userInfo
with value as the notification
under a known key.
While observing, we will check for typedNotification
in userInfo
using the same key. Once we have our TypedNotification
object, we will pass it on to the block.
extension NotificationCenter : TypedNotificationCenter {
static var typedNotificationUserInfoKey = "_TypedNotification"
func post<N>(_ notification: N) where N : TypedNotification {
post(name: N.notificationName, object: notification.sender,
userInfo: [
NotificationCenter.typedNotificationUserInfoKey : notification
])
}
func addObserver<N>(_ forType: N.Type, sender: N.Sender?, queue: OperationQueue?, using block: @escaping (N) -> Void) -> NSObjectProtocol where N : TypedNotification {
return addObserver(forName: N.notificationName, object: sender, queue: queue) { n in
guard let typedNotification = n.userInfo?[NotificationCenter.typedNotificationUserInfoKey] as? N else {
fatalError("Could not construct a typed notification: \(N.name) from notification: \(n)")
}
block(typedNotification)
}
}
}
Trying it Out
Here we create a sample notification with some strongly typed data and subscribe to it. Notice that the observer is passed the actual notification instance, making it easy to extract the data associated with the notification in a type-safe way.
struct SubscriptionsChanged : TypedNotification {
var sender: Any
static var name = "subscriptionsChanged"
var subscribed: Set<Int> = []
var unsusbscribed: Set<Int> = []
}
_ = NotificationCenter.default.addObserver(SubscriptionsChanged.self, sender: self, queue: nil) { notification in
print("Subscribed to: ", notification.subscribed)
print("Unsubscribed from: ", notification.unsusbscribed)
}
NotificationCenter.default.post(SubscriptionsChanged(sender: self, subscribed: [123], unsusbscribed: [567]))