lin*_*ram 4 macos appkit actor swift swift-concurrency
我正在注释我的函数,@MainActor以确保可以从任何异步位置安全地调用它以触发 UI 更新。尽管如此,我还是遇到了一个错误,不知何故,UI 更新似乎是在后台线程上尝试的,即使(据我理解)该函数严格绑定到`@MainActor\xc2\xb4.
这是我的代码:
\n/// Dismisses the popup from its presenting view controller.\n@MainActor public func dismiss() {\n presentingViewController?.dismiss(self)\n}\nRun Code Online (Sandbox Code Playgroud)\n它是从 an 内部调用的,NSViewController它使用 监听某个事件NotificationCenter,然后在以下objc函数中启动解雇:
class MainWindowControllerVC: NSWindowController, NSWindowDelegate {\n override func windowDidLoad() {\n NotificationCenter.default.addObserver(self, selector: #selector(self.dismissVCastSharePopup), name: .NOTIF_DISMISS_POPUP, object: nil)\n }\n\n @objc private func dismissPopup() {\n // some other cleanup is happening here\n popup?.dismiss()\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n我收到以下错误:
\n*** Terminating app due to uncaught exception \'NSInternalInconsistencyException\', reason: \'NSWindow drag regions should only be invalidated on the Main Thread!\'\nRun Code Online (Sandbox Code Playgroud)\n以及以下警告:
\n\n有人可以解释一下这怎么可能吗?我在这里误解了什么?如果我将相同的代码包装到 aDispatchQueue.main.async或什至Task\xc2\xa0{ @MainActor () -> Void in ... }我不会收到此错误。它与函数之前的注释特别相关。
太长了;博士
\n当您将函数隔离到 时@MainActor,仅当您从 Swift 并发上下文调用此方法时才相关。如果您从 Swift 并发系统外部(例如从NotificationCenter)调用它,则@MainActor限定符不起作用。
因此,要么从 Swift 并发上下文中调用这个 actor 隔离函数,要么依赖遗留main队列模式。
有多种方法可以解决这个问题。首先,让我将您的示例重构为 MCVE:
\nimport Cocoa\nimport os.log\n\nprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ViewController")\n\nextension Notification.Name {\n static let demo = Notification.Name(rawValue: Bundle.main.bundleIdentifier! + ".demo")\n}\n\nclass ViewController: NSViewController {\n deinit {\n NotificationCenter.default.removeObserver(self, name: .demo, object: nil)\n }\n\n override func viewDidLoad() {\n super.viewDidLoad()\n\n addObserver()\n postFromBackground()\n }\n\n func addObserver() {\n logger.debug(#function)\n\n NotificationCenter.default.addObserver(self, selector: #selector(notificationHandler(_:)), name: .demo, object: nil)\n }\n\n func postFromBackground() {\n DispatchQueue.global().asyncAfter(deadline: .now() + 1) {\n logger.debug(#function)\n NotificationCenter.default.post(name: .demo, object: nil)\n }\n }\n\n @objc func notificationHandler(_ notification: Notification) {\n checkQueue()\n }\n\n @MainActor func checkQueue() {\n logger.debug(#function)\n dispatchPrecondition(condition: .onQueue(.main))\n logger.debug("OK")\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n有几种方法可以解决这个问题:
\n我的notificationHandler(dismissVCastSharePopup在你的例子中)不在 Swift 并发上下文中。但我可以通过将调用包装在一个 Swift 并发中Task {\xe2\x80\xa6}:
@objc nonisolated func notificationHandler(_ notification: Notification) {\n Task { await checkQueue() }\n}\nRun Code Online (Sandbox Code Playgroud)\n请注意,我不仅将调用包装在 a 中Task {\xe2\x80\xa6},而且还添加了一个nonisolated限定符,让编译器知道这是从非隔离上下文调用的。(我认为应该从@objc限定符中推断出这一点,但目前还没有。)
或者,您可以退回到遗留模式以确保您位于主线程上,例如基于块的模式,addObserver它允许您指定.main队列:
private var observerToken: NSObjectProtocol?\n\nfunc addObserver() {\n observerToken = notificationCenter.addObserver(forName: .demo, object: nil, queue: .main) { [weak self] _ in\n self?.checkQueue()\n }\n\n \xe2\x80\xa6\n}\nRun Code Online (Sandbox Code Playgroud)\n也许不用说,即使我们@objc在后一个例子中不再使用选择器方法,这个闭包也不会在参与者隔离的上下文中被调用,因此它将忽略该@MainActor属性。上面的模式之所以有效,只是因为我遵循了遗留模式并明确指定了.main队列来处理该观察者。