Swift 并发:@MainActor 对象上的通知回调

Bry*_*yan 6 appkit async-await swift swift-concurrency

语境

在使用 Swift 5.x 和 Xcode 14 构建的 Mac 应用程序中,我有一个控制器对象。该对象具有@PublishedSwiftUI 视图观察到的多个属性,因此我将该对象放置在@MainActor这样的位置:

@MainActor
final class AppController: NSObject, ObservableObject
{
    @Published private(set) var foo: String = ""
    @Published private(set) var bar: Int = 0

    private func doStuff() {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

问题

当Mac进入睡眠状态时,这个应用程序需要采取某些操作,因此我在方法中订阅了适当的通知init(),但由于AppController用 装饰@MainActor,我收到此警告:

override init()
{
    NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.willSleepNotification, object: nil, queue: .main) { [weak self] note in
        self?.doStuff()     // "Call to main actor-isolated instance method 'doStuff()' in a synchronous nonisolated context; this is an error in Swift 6"
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,我试图隔离它。但是(当然)编译器有一些新的问题需要抱怨。这次报错了:

override init()
{
    NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.willSleepNotification, object: nil, queue: .main) { [weak self] note in
        Task { @MainActor in
            self?.doStuff()    // "Reference to captured var 'self' in concurrently-executing code
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以我这样做是为了解决这个问题:

override init()
{
    NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.willSleepNotification, object: nil, queue: .main) { [weak self] note in
      
        let JUSTSHUTUP: AppController? = self 
        Task { @MainActor in
            JUSTSHUTUP?.doStuff()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

问题

最后一位不会产生编译器错误并且似乎可以工作。但我不知道这是否正确或最佳实践。

我确实理解编译器为什么会抱怨以及它试图保护我免受什么影响,但尝试在现有项目中采用 Swift Concurrency 是......痛苦的。

Rob*_*Rob 6

您可以使用您的Task { @MainActor in ... }模式,但将[weak self]捕获列表添加到Task

\n
NSWorkspace.shared.notificationCenter.addObserver(\n    forName: NSWorkspace.willSleepNotification,\n    object: nil,\n    queue: .main\n) { [weak self] note in\n    Task { @MainActor [weak self] in\n        self?.doStuff()\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

FWIW,在 Swift 并发中,我们可能会放弃旧的基于完成处理程序的观察者,而不是观察者模式,而是使用异步序列,notifications(named:object:)

\n
@MainActor\nfinal class AppController: ObservableObject {\n    private var notificationTask: Task<Void, Never>?\n\n    deinit {\n        notificationTask?.cancel()\n    }\n\n    init() {\n        notificationTask = Task { [weak self] in\n            let sequence = NSWorkspace.shared.notificationCenter.notifications(named: NSWorkspace.willSleepNotification)\n\n            for await notification in sequence {\n                self?.doStuff(with: notification)\n            }\n        }\n    }\n\n    private func doStuff(with notification: Notification) { \xe2\x80\xa6 }\n}\n
Run Code Online (Sandbox Code Playgroud)\n