62 lines
2.6 KiB
Swift
62 lines
2.6 KiB
Swift
//
|
|
// Copyright 2021 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
|
|
// We often use notifications as way to publish events.
|
|
//
|
|
// We never need these events to be received synchronously,
|
|
// so we should always send them asynchronously to avoid any
|
|
// possible risk of deadlock. These methods also ensure that
|
|
// the notifications are always fired on the main thread.
|
|
extension NotificationCenter {
|
|
|
|
/// Waits until `name`/`object` is posted.
|
|
public func observeOnce(_ name: Notification.Name, object: Any? = nil) async {
|
|
await withCheckedContinuation { continuation in
|
|
// Concurrency in this method is nontrivial. There are at least two
|
|
// relevant race conditions:
|
|
//
|
|
// (1) Multiple threads could post a notification at the same time, and
|
|
// this will trigger multiple callbacks, even if you call `removeObserver`
|
|
// as quickly as possible. We guard against this with an atomic
|
|
// compare-and-swap to set the observer to nil.
|
|
//
|
|
// (2) When initially registering the observer, it's possible that another
|
|
// thread could be posting the notifiation at the same time. If the
|
|
// notification callback happens before we've assigned the initial value to
|
|
// the atomic observer value, we'll drop that notification. We avoid that
|
|
// problem by ensuring that the `addObserver` call and assignment of the
|
|
// observer happen atomically.
|
|
//
|
|
// Memory management in this method is also non-trivial. The observer
|
|
// captured in the block must be set to nil to avoid leaking memory.
|
|
let observer = AtomicOptional<NSObjectProtocol>(nil, lock: .init())
|
|
_ = observer.map { _ in
|
|
return addObserver(forName: name, object: object, queue: nil, using: { [weak self] notification in
|
|
guard let observer = observer.swap(nil) else {
|
|
return
|
|
}
|
|
self?.removeObserver(observer)
|
|
continuation.resume(returning: ())
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc
|
|
public func postNotificationNameAsync(_ name: Notification.Name, object: Any?) {
|
|
DispatchQueue.main.async {
|
|
self.post(name: name, object: object)
|
|
}
|
|
}
|
|
|
|
@objc
|
|
public func postNotificationNameAsync(_ name: Notification.Name, object: Any?, userInfo: [AnyHashable: Any]? = nil) {
|
|
DispatchQueue.main.async {
|
|
self.post(name: name, object: object, userInfo: userInfo)
|
|
}
|
|
}
|
|
}
|