我试图找出使用 SwiftUI 和 Swift 5.5 并发功能的 macOS 文档应用程序的正确结构。
我想演示以线程安全的方式异步更新文档的数据,并能够在文件中读取/写入数据,同时也是线程安全的并且在后台运行。然而,我正在努力:
- 编写干净的代码 - 与我之前使用的应用程序相比,其中一些看起来不优雅,更像是
DispatchQueues
笨重 - 为参与者实现
Codeable
一致性
我正在寻求关于如何改进这一点的想法、更正和建议。我已经在GitHub 上发布了完整的代码,因为我只会在这里突出一些特定的元素。这是一个最小可行的应用程序,仅用于概念验证。
该应用程序
该应用程序显示一个Records
带有添加更多按钮的列表。它应该能够从文件中保存和重新加载列表。
当前方法/设计
我选择了ReferenceFileDocument
该类型的协议Document
,因为这是我将在具有更复杂数据结构的未来应用程序中使用的协议。(即我不打算使用纯的集合structs
来保存文档的数据)
Document
具有表示顶级数据结构content
的类型属性。RecordsModelView
RecordsModelView
被注释@MainActor
以确保它收到的任何更新都将在主线程上处理。
RecordsModelView
有一个类型的属性RecordsModel
。这是一个参与者,确保其数组的读/写Records
是线程安全的,但不通过 MainActor 协调以提高效率。
该应用程序假定添加项目的函数需要很长时间,因此使用Task
. 虽然这里没有演示,但我也假设addRecord
可能从多个后台线程调用,因此需要是线程安全的,因此使用actor
.
代码编译并运行,允许将新项目添加到列表中,但是......
问题
首先,我无法注释- 生成我无法解决的编译器错误Document
。@MainActor
如果可以的话,我认为它可能会解决我的一些问题......
其次,因此我有一种笨拙的方式Document
来初始化其内容属性(这也必须是可选的才能使其工作)。这看起来很讨厌,并且具有需要在引用它的任何地方打开它的连锁反应:
final class Document: ReferenceFileDocument {
@Published var content: RecordsViewModel?
init() {
Task { await MainActor.run { self.content = RecordsViewModel() } }
}
// Other code here
}
最后,我无法让RecordsModel
to 符合Encodable
. 我尝试过encode(to encoder: Encoder)
异步,但这并不能解决问题。目前,因此RecordsModel
只是符合Decodable
.
func encode(to encoder: Encoder) async throws { // <-- Actor-isolated instance method 'encode(to:)' cannot be used to satisfy a protocol requirement
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(records, forKey: .records)
}