[英]Swift Concurrency: Notification Callbacks on @MainActor Objects
In a Mac app built with Swift 5.x and Xcode 14, I have a controller object. This object has several @Published
properties that are observed by SwiftUI views, so I have placed the object on @MainActor
like this:在使用Swift 5.x和Xcode 14构建的Mac应用程序中,我有一个
@Published
@MainActor
@MainActor
final class AppController: NSObject, ObservableObject
{
@Published private(set) var foo: String = ""
@Published private(set) var bar: Int = 0
private func doStuff() {
...
}
}
This app needs to take certain actions when the Mac goes to sleep, so I subscribe to the appropriate notification in the init()
method, but because AppController
is decorated with @MainActor
, I get this warning:当 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"
}
}
So, I attempted to isolate it.所以,我试图隔离它。 But (of course) the compiler has something new to complain about.
但是(当然)编译器有一些新的抱怨。 This time an error:
这次报错:
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
}
}
}
So I did this to solve that:所以我这样做是为了解决这个问题:
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()
}
}
}
The last bit produces no compiler errors and seems to work.最后一位没有产生编译器错误并且似乎可以工作。 But I have NO idea if it's correct or best-practice.
但我不知道它是正确的还是最佳实践。
I do understand why the compiler is complaining and what it's trying to protect me from, but attempting to adopt Swift Concurrency in an existing project is...painful.我确实理解为什么编译器会抱怨以及它试图保护我免受什么影响,但是尝试在现有项目中采用 Swift 并发是......痛苦的。
You can use your Task { @MainActor in... }
pattern, but add the [weak self]
capture list to the Task
:您可以使用您的
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, rather than the observer pattern, in Swift concurrency, we would probably forgo the old completion-handler-based observer, and instead use the asynchronous sequence, notifications(named:object:)
: 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) { … }
}
An alternative is to use Combine
另一种方法是使用
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()
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.