语境

在一个使用Swift 5.x和Xcode14构建的Mac应用程序中,我有一个控制器对象.该对象具有SwiftUI视图可以观察到的几个@Published个属性,因此我将该对象放置在@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: NSObject, ObservableObject {
    private var task: Task<Void, Never>?

    deinit {
        task?.cancel()
    }

    func cancelActiveEvolution() {
        task?.cancel()              // in case you accidentally call this more than once

        task = Task { [weak self] in
            let sequence = NSWorkspace.shared.notificationCenter.notifications(named: NSWorkspace.willSleepNotification)

            for await notification in sequence {
                self?.handleNotification(notification)
            }
        }
    }

    private func handleNotification(_ notification: Notification) {
        ...
    }
}

Swift相关问答推荐

.onReceive NSWindow.CloseNotify是否会为App中的每个窗口调用?

按SWIFT中每个ID的最大值进行筛选/排序

并发访问SWIFT中数组的非重叠子范围

按下一步后无法让文本字段切换焦点

UICollectionViewCompostionalLayout Collectionview单元格宽度在重新加载数据后未正确调整大小

如何在SwiftUI中做出适当的曲线?

为什么 Swift URL 的 init?(string: String,relativeTo: URL?) 仅添加协议?

当将新值推送到 NavigationStack 时,SwiftUI 的 navigationDestination 已在堆栈的早期声明

在表单中对齐文本框

设备上ScrollView为什么会将内容高度更改为随机值,而在预览上不会? - 优化后的标题:设备上ScrollView内容高度随机变化问题解决

避免 `(())` 显式指定 Void 类型的枚举关联值

为什么我在我的 Swift iOS 应用程序项目中收到 Xcode 中的错误消息无法识别的 Select 器发送到类?

Swift // Sprite Kit:类别位掩码限制

Swift - 订阅视图之外的绑定值

使用自定义相机拍照 iOS 11.0 Swift 4. 更新错误

如何在 Swift 中使用 Crashlytics 登录?

URLComponents.url 为零

如何快速从另一个视图控制器重新加载表格视图

将 UIImage 剪成圆形

从 Swift 初始化程序调用方法