简体   繁体   English

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

[英]Swift Concurrency: Notification Callbacks on @MainActor Objects

Context语境

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

Problem问题

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

Question

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM