`NSDocument` 的 `data(ofType:)` 从(异步)`actor` 获取数据

`NSDocument`'s `data(ofType:)` getting data from (async) `actor`

我有一个基于 macOS 的文档,它使用基于 NSDocument 的子类。

为了写入文档的文件,我需要实施 data(ofType:) -> Data,这应该 return 将文档的数据存储在磁盘上。这是(当然)同步函数。

我的数据模型是一个 actor,其函数 return 是 Data 表示。

现在的问题是我需要await这个功能,但是data(ofType:)想要同步数据。

如何强制等待(阻塞主线程)直到 actor 完成其工作并获取数据?

编辑: 根据 Sweepers 的评论,这可能是一个 XY 问题,我尝试将模型设为 @MainActor,因此文档可以直接访问属性。然而,这不允许我首先创建模型:

@MainActor class Model {}

class Document: NSDocument {
let model = Model() <- 'Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context'
}

然后我尝试将整个 Document 设为 @MainActor,但这会使我的整个应用程序因编译器错误而崩溃。即使是最简单的调用也需要异步执行。这不允许任何类型的新并发系统升级路径。

过去我的模型受到串行后台队列的保护,我基本上可以queue.sync {}安全地取出所需的数据(暂时阻塞主队列)。

我已经研究了 saveToURL:ofType:forSaveOperation:completionHandler:,我认为我可以根据需要使用它。它允许异步消息传递保存已完成,所以我现在覆盖此方法并在异步 Task 中从模型中获取数据并将其临时存储。然后我调用 super,它最终调用 data(forType:) 我 return 数据。

根据@Willeke 在评论中的想法,我想出了以下解决方案:

private var snapshot: Model.Snapshot?

override func save(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType, completionHandler: @escaping (Error?) -> Void) {
    //Get the data and continue later
    Task {
        snapshot = await model.getSnapshot()
        super.save(to: url, ofType: typeName, for: saveOperation, completionHandler: completionHandler)
    }
}

override func data(ofType typeName: String) throws -> Data {
    defer { snapshot = nil }

    guard let snapshot = snapshot else {
        throw SomeError()
    }

    let encoder = JSONEncoder()
    let data = try encoder.encode(snapshot)

    return data
}

由于save()函数准备异步处理保存结果,我们首先对数据进行快照,然后让保存函数继续。