5.3 更新你的基于文稿的应用
5 分钟阅读
原文链接: https://developer.apple.com/documentation/swiftui/updating-your-document-based-app
5.3 更新你的基于文稿的应用
把一个已有应用迁移到使用 URL 的方式读写文稿,并结合 Swift 并发。
概述
如果你已有一个基于文稿的应用,可以采用 Document 协议,从而获得直接 URL 访问、与 Swift 并发集成以及现代观察机制等好处。Document 协议把读和写分离到各自的专用类型中,让你对文件 I/O 有更多控制,并能对复杂的文稿格式实现部分读取和写入。
在早于 iOS 27、iPadOS 27、macOS 27 和 visionOS 27 的版本中,你通过遵循 FileDocument 或 ReferenceFileDocument 来创建文稿类型。在这些以及更新的版本中,你可以依据应用的实际需要,遵循 Document 协议,或者分别遵循 ReadableDocument 与 WritableDocument 协议。尽管 FileDocument 和 ReferenceFileDocument 仍然可用,但它们已不再支持用于新的文稿类型。
下表列出这三个协议之间的差异,帮助你选择合适的迁移路径:
| FileDocument | ReferenceFileDocument | Document | |
|---|---|---|---|
| 类型 | 值类型(struct) | 引用类型(class) | 引用类型(class) |
| 读取 | init(configuration:) | init(configuration:) | reader(configuration:) + apply(snapshot:previous:) |
| 写入 | fileWrapper(configuration:) | fileWrapper(snapshot:configuration:) | writer(configuration:) + snapshot(contentType:) |
| 读写执行方式 | 同步 | 同步 | async/sending,并有显式的 actor 边界 |
| 文件访问 | 仅 FileWrapper | 仅 FileWrapper | URL 或 FileWrapper |
| 撤销 | 自动(值语义) | 手动(UndoManager) | 手动(UndoManager) |
| 观察机制 | 不适用(值类型) | ObservableObject | @Observable |
更新你的应用
依据应用使用的是哪种已弃用协议,选择相应的标签页并按照清单更新应用:
FileDocument
- 从结构体改为 @Observable 类。 遵循 FileDocument 的文稿类型通常是值类型。Document 协议要求引用类型。请把你的结构体替换为用 @Observable 标注的
final class。 - 把读取逻辑分离到 DocumentReader 中。 读取时使用 FileWrapperDocumentReader,并提供一个把 FileWrapper 转换成快照值的闭包。
- 实现 apply(snapshot:previous:)。 当读取器送来新的快照时,用这个方法更新文稿的属性。
- 把写入逻辑分离到 DocumentWriter 中。 写入时使用 FileWrapperDocumentWriter,并提供一个把快照转换成 FileWrapper 的闭包。
- 实现 snapshot(contentType:)。 添加这个方法,在主 actor 上捕获文稿的当前状态。把它标记为
async throws,并返回sending值。 - 添加撤销注册。 使用 FileDocument 时,SwiftUI 通过值语义和 Binding 自动管理撤销。使用 Document 协议时,你需要用 UndoManager 自己注册撤销操作。内容视图环境中取得的撤销管理器已经与该文稿连接。
- 更新你的 DocumentGroup 构造器。 把
DocumentGroup(newDocument:)替换为基于闭包、接收 URLDocumentConfiguration 和 DocumentCreationContext 的构造器。 - 更新你的内容视图。 把
@Binding var document: MyDocument替换为对可观察类的直接引用,并用 @Bindable 创建绑定。
ReferenceFileDocument
- 把文稿标注为 @Observable。 ReferenceFileDocument 协议早于 Observation 框架。请添加 @Observable 宏,并移除任何 ObservableObject 遵循和 @Published 属性包装器。
- 把读取逻辑分离到 DocumentReader 中。 读取时使用 FileWrapperDocumentReader,并提供一个把 FileWrapper 转换成快照值的闭包。
- 实现 apply(snapshot:previous:)。 当读取器送来新的快照时,用这个方法更新文稿的属性。
- 把写入逻辑分离到 DocumentWriter 中。 写入时使用 FileWrapperDocumentWriter,并提供一个把快照转换成 FileWrapper 的闭包。
- 实现 snapshot(contentType:)。 更新该方法,为返回类型加上
async throws和sending,以便安全跨越并发边界传递。 - 更新你的 DocumentGroup 构造器。 把基于类型的构造器替换为基于闭包、接收 URLDocumentConfiguration 和 DocumentCreationContext 的构造器。
- 检查撤销注册。 撤销注册在概念上是一样的,但要确认这些改动之后你的撤销操作仍然正常工作。
如果你现有的基于文稿的应用使用 FileDocument 或 ReferenceFileDocument,下表展示这些概念如何映射到 Document 协议:
FileDocument
| 迁移前 | 迁移后 |
|---|---|
| FileDocument | Document |
struct(值类型) | @Observable final class(引用类型) |
| init(configuration:) | 独立的 DocumentReader |
| fileWrapper(configuration:) | 独立的 DocumentWriter |
| 隐式快照(值语义) | 显式的 snapshot(contentType:) 方法 |
| 通过 Binding 自动撤销 | 用 UndoManager 手动注册撤销 |
DocumentGroup(newDocument:) | DocumentGroup 构造器 |
ReferenceFileDocument
| 迁移前 | 迁移后 |
|---|---|
| ReferenceFileDocument | Document |
| init(configuration:) | 独立的 DocumentReader |
| fileWrapper(snapshot:configuration:) | 独立的 DocumentWriter |
| 仅 FileWrapper | FileWrapper 以及通过 DocumentReader 的 URL 访问 |
ReferenceFileDocument 上的单一 Snapshot 类型 | DocumentWriter 和 DocumentReader 上各自的 Snapshot 类型 |
下面的例子展示了一个完整的文本文稿在从 FileDocument 迁移前后的样子:
迁移前
| |
迁移后
| |
关键的结构性变化是从值语义转向引用语义。使用 FileDocument 时,SwiftUI 通过指向结构体的 Binding 跟踪变更,并自动管理撤销。使用 Document 时,你用 @Observable 跟踪变更并显式注册撤销操作——但换来的是异步 I/O、基于 URL 的文件访问,以及状态捕获与序列化之间的清晰分离。
下面的例子展示了一个完整的文本文稿在从 ReferenceFileDocument 迁移前后的样子:
迁移前
| |
迁移后
| |
迁移后的文稿清晰地分离了各项职责,既支持用于高级场景的 URL 访问,也采用了 Observation 框架,并与 Swift 并发集成。