-
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 theNotifiable
protocol 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.