-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Notifable Protocol and the NotificationCenter
Any type which needs to respond to NSNotificationCenter notifications should conform to the Notifiable protocol. This type provides some threading protections that will help prevent runtime crashes for types which also use Swift Concurrency.
Note: Swift 6.2 adds additional protections around
NSNotificationCenter, especially for threading with Swift Concurrency, so we may be able to retire theNotifiableprotocol at a later time.
The Notifiable protocol ensures that the @objc #selector method (which fires in response to posted notifications) is nonisolated. This is because (as of 2025 with our Swift 6.0 code migration) it is possible to annotate the @objc method used in a #selector with @MainActor to please the compiler.
However, at run time, the #selector method will run on the same thread as the notification was posted, violating @MainActor promises.
As an unrelated benefit, using Notifiable means you do not have to worry about removing your notification observers before your type is deallocated.
Your type should conform to Notifiable.
class MyType: Notifiable {
...
}Upon initialization of your type, you should also pass in a NotificationProtocol, which will be used by the Notifiable methods. This is a thin wrapper for NotificationCenter.default which allows unit tests to mock out the notification center.
class MyType: Notifiable {
...
var notificationCenter: NotificationProtocol
init(notificationCenter: NotificationProtocol = NotificationCenter.default) {
...
self.notificationCenter = notificationCenter
}
...
}Register for the notifications your type wishes to listen to.
Most likely, you will call this in your type's init or viewDidLoad.
startObservingNotifications(
withNotificationCenter: notificationCenter,
forObserver: self,
observing: [UIApplication.didBecomeActiveNotification,
UIApplication.willResignActiveNotification,
UIApplication.didEnterBackgroundNotification]
)To conform to Notifiable, you must implement the handleNotifications() method.
When any of the registered notifications are posted, the Notifiable's handleNotifications() method will be called.
// MARK: - Notifiable
func handleNotifications(_ notification: Notification) {
switch notification.name {
case UIApplication.didEnterBackgroundNotification:
ensureMainThread {
if self.didTapButton {
self.dismiss(animated: false)
self.buttonTappedFinishFlow?()
}
}
...
...
...
default:
break
}
}Note: This method inherits @objc and nonisolated from the Notifiable protocol definition:
@objc
nonisolated func handleNotifications(_ notification: Notification) {
...
}You can use a switch statement to handle each type of notification.
For work that must happen on the main thread, you have two options:
- (Preferred) Use
ensureMainThread { ... }to isolate your work to the main actor using GCD. - Use
Task { @MainActor in ... }to isolate your work with Swift Concurrency
Why is option 1 preferred? handleNotifications() will be called on the main thread if a notification is posted on the main thread. In that case, the ensureMainThread() method performs your UI updates on the same run loop. This is preferable to waiting for the next run loop in certain situations where the timing of events might be important in our legacy code base.
No. Since Notifiable uses #selector variant of the NotificationCenter's addObserver() method, conforming types are not required to remove observers. See the documentation from Apple.