我用@MainActor
注解我的函数,以确保它可以从任何异步位置安全地调用,以触发UI更新。尽管如此,我遇到了一个错误,不知何故,UI更新似乎是在后台线程上尝试的,即使(据我理解)该函数严格绑定到@MainActor
。
这是我的代码:
/// Dismisses the popup from its presenting view controller.
@MainActor public func dismiss() {
presentingViewController?.dismiss(self)
}
它是从NSViewController
内部调用的,NotificationCenter
监听某个事件,然后在下面的objc
函数中启动解除:
class MainWindowControllerVC: NSWindowController, NSWindowDelegate {
override func windowDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(self.dismissVCastSharePopup), name: .NOTIF_DISMISS_POPUP, object: nil)
}
@objc private func dismissPopup() {
// some other cleanup is happening here
popup?.dismiss()
}
}
我得到以下错误:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'NSWindow drag regions should only be invalidated on the Main Thread!'
以及以下警告:
有人能解释一下这是怎么可能的吗?我误会什么了?如果我将相同的代码 Package 成DispatchQueue.main.async
甚至Task { @MainActor () -> Void in ... }
,我不会得到这个错误。它特别地与函数之前的注解绑定。
1条答案
按热度按时间atmip9wb1#
博士
当你将一个函数隔离到
@MainActor
时,只有当你从Swift并发上下文调用这个方法时,这才是相关的。如果你从Swift并发系统外部调用它(比如从NotificationCenter
),@MainActor
限定符没有任何作用。因此,要么从Swift并发上下文调用这个actor隔离函数,要么依赖于遗留的
main
队列模式。有很多方法可以解决这个问题。首先,让我将您的示例重构为MCVE:
有几种方法可以解决这个问题:
1.我的
notificationHandler
(在你的例子中是dismissVCastSharePopup
)不在Swift并发上下文中。但我可以通过将调用 Package 在Task {…}
中来桥接Swift并发:注意,我不仅将调用 Package 在
Task {…}
中,还添加了nonisolated
限定符,以让编译器知道这是从非隔离上下文调用的。(我认为它应该从@objc
限定符中推断出来,但目前还没有。)1.或者,你可以回到传统模式来确保你在主线程上,比如基于块的
addObserver
,它允许你指定.main
队列:也许不用说,即使我们在后面的例子中不再使用
@objc
选择器方法,这个闭包也不会在actor隔离的上下文中被调用,因此它将忽略@MainActor
属性。上面的模式之所以能够工作,完全是因为我遵循了遗留模式,并显式地指定了.main
队列来处理这个观察者。