如果我需要监听通知,我总是这样做:
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: UIApplication.didEnterBackgroundNotification, object: nil)
Run Code Online (Sandbox Code Playgroud)
但是,如果我在异步方法体内移动该行,编译器突然希望将该行标记为等待:
func setup() async {
let importantStuff = await someTask()
// store my important stuff, now setup is complete
// This line won't compile without the await keyword.
// But addObserver is not even an async method
await NotificationCenter.default.addObserver(self, selector: #selector(foo), name: UIApplication.didEnterBackgroundNotification, object: nil)
// But this works fine
listen()
}
func listen() {
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
Run Code Online (Sandbox Code Playgroud)
最小的例子
此代码无法编译(编译器想要添加awaitaddObserver 调用)。
class Test {
func thisFailsToCompile() async {
let stuff = try! await URLSession.shared.data(from: URL(string: "https://google.com")!)
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
@objc func foo() {
}
}
Run Code Online (Sandbox Code Playgroud)
这里发生了什么?
问题是 UIApplication 是@MainActor并且didEnterBackgroundNotification是它的一个属性,并且它没有被标记nonisolated,所以它只能在主要参与者上访问。这可能是 Apple 的疏忽,因为didEnterBackgroundNotification是一个常数,并且应该能够被标记nonisolated。但这也可能是 ObjC/Swift 桥接器的限制。
根据您希望它如何工作,有几种修复方法。
影响最小的更改(除了保留await)是同步缓存该值:
class Test {
// Cache the value of didEnterBackgroundNotification so that async code doesn't have to access UIApplication
private let didEnterBackgroundNotification = UIApplication.didEnterBackgroundNotification
func thisFailsToCompile() async {
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: didEnterBackgroundNotification, object: nil)
}
@objc func foo() { }
}
Run Code Online (Sandbox Code Playgroud)
或者,您可以标记Test为@MainActor. “UI”的东西几乎总是应该是@MainActor,所以这取决于它是什么类型的对象。
@MainActor
class Test { ... }
Run Code Online (Sandbox Code Playgroud)
或者,您可以将相关方法标记为@MainActor(尽管这可能不是解决您问题的好方法):
@MainActor func thisFailsToCompile() async {
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
Run Code Online (Sandbox Code Playgroud)
请注意,通知是在调用的队列上同步传递的postNotification。这意味着所有 UIKit(包括 UIApplication)通知都会到达主队列。事实上,大多数通知都来自主队列。这不是必需的或强制执行的(因此应用程序中的本地通知可能会出现在随机队列中),但这很常见。因此,如果您监听通知,那么创建该对象通常非常有用@MainActor。
(但希望苹果也能解决这种关于各种常量被隔离的愚蠢问题。)