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



上下文

在一个用Swift 5.x和Xcode 14构建的Mac应用程序中,我有一个控制器对象。这个对象有几个@Published属性,SwiftUI视图可以观察到这些属性,所以我把这个对象放在@MainActor上,如下所示:

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

问题

当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"
}
}

所以,我试图隔离它。但是(当然)编译器有一些新的东西需要抱怨。这次出现错误:

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
}
}
}

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

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()
}
}
}

问题

最后一位没有产生编译器错误,并且似乎可以工作。但我不知道这是正确的还是最好的做法。

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

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

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

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

@MainActor
final class AppController: ObservableObject {
private var notificationTask: Task<Void, Never>?
deinit {
notificationTask?.cancel()
}
init() {
notificationTask = Task { [weak self] in
let sequence = NSWorkspace.shared.notificationCenter.notifications(named: NSWorkspace.willSleepNotification)
for await notification in sequence {
self?.doStuff(with: notification)
}
}
}
private func doStuff(with notification: Notification) { … }
}

另一种选择是使用Combine

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

private var cancellable : AnyCancellable?

private func doStuff() {
//
}

override init()
{
super.init()
cancellable = NSWorkspace.shared.notificationCenter
.publisher(for: NSWorkspace.willSleepNotification)
.receive(on: DispatchQueue.main)
.sink { [weak self] note in
self?.doStuff()
}
}
}

相关内容

  • 没有找到相关文章