用 SwiftData 构建应用
Build an app with SwiftData
2023年6月5日
一句话判断
这是一场手把手教学——用闪卡应用做实例,演示如何用 @Model、@Query、ModelContainer 三板斧在 SwiftUI 中集成 SwiftData,从零到完整持久化只需要改几行代码。
这场 Session 讲了什么
SwiftUI 工程师 Julia 以一个闪卡(flashcards)应用为实例,演示了 SwiftData 和 SwiftUI 的无缝集成。整个 Session 是 code-along 格式,提供了配套的 Xcode 起始项目和完成项目。
模型改造极简:只需给现有类加上 @Model 宏,它就自动获得 SwiftData 的持久化能力和 Observable 协议的一致性。不需要手写 Codable,不需要 @Published 属性包装器,不需要 ObservableObject。
@Query 查询:用 @Query 替换 @State,视图就能直接查询 SwiftData 存储中的模型。@Query 会自动在模型数据变化时触发视图更新。支持排序、过滤和动画配置。
ModelContainer 配置:通过 .modelContainer() 视图修饰符为视图层级提供数据源。整个 WindowGroup 共享一个容器,或不同视图使用不同容器。没有配置 ModelContainer 的视图无法使用 SwiftData。
自动保存:SwiftData 会自动在 UI 事件和用户输入时保存 ModelContext,不需要手动调用 modelContext.save()。
Bindable 绑定:配合新的 @Bindable 属性包装器,可以直接将模型的可变属性绑定到 UI 控件(如 TextField),数据流代码比以前更少。
Session 还展示了如何用 SwiftData 创建基于文档的应用——SwiftData 自动处理文档存储,极大地简化了文档型应用的开发。
值得深挖的点
@Model 宏的复合能力:一个宏同时解决了持久化(自动实现 Codable 等协议)、可观察性(自动遵循 Observable)和变更追踪。对比 Core Data 需要的 NSManagedObject 子类、手动属性声明和 NSFetchedResultsController,代码量差距巨大。
@Query 的灵活性:每个视图可以有多个 @Query 属性,各自独立配置排序和过滤条件。@Query 底层使用视图的 ModelContext 作为数据源,不需要额外的数据库连接管理。
ModelContainer 的层级设计:可以在 WindowGroup 级别设置(所有窗口共享),也可以在单个视图级别设置(不同视图用不同存储栈)。这种灵活性意味着同一个应用可以有多个独立的存储空间——比如主数据用一个容器,设置数据用另一个。
Preview 的数据支持:Session 展示了如何创建内存中的 ModelContainer 来填充 Preview 数据。这对开发体验很重要——你不需要每次启动应用才能看到数据效果。
代码片段
// 1. 模型定义 - 只需加 @Model 宏
import SwiftData
@Model
class Card {
var frontText: String
var backText: String
var creationDate: Date
init(frontText: String, backText: String) {
self.frontText = frontText
self.backText = backText
self.creationDate = Date()
}
}
// 2. 查询和显示
struct ContentView: View {
// 用 @Query 替换 @State,自动查询并响应数据变化
@Query(sort: \Card.creationDate, order: .reverse)
var cards: [Card]
var body: some View {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 150))]) {
ForEach(cards) { card in
CardView(card: card)
}
}
}
}
// 3. 应用入口配置 ModelContainer
@main
struct FlashCardsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
// 为整个 WindowGroup 设置 ModelContainer
.modelContainer(for: Card.self)
}
}
// 4. 创建和保存模型
struct AddCardView: View {
@Environment(\.modelContext) private var modelContext
@Bindable var card: Card // 用 Bindable 直接绑定
var body: some View {
Form {
TextField("正面", text: $card.frontText)
TextField("背面", text: $card.backText)
}
}
func saveCard() {
let newCard = Card(frontText: "问题", backText: "答案")
modelContext.insert(newCard)
// 不需要手动 save() - SwiftData 自动保存
}
}
// 5. Preview 用的内存容器
#Preview {
ContentView()
.modelContainer(
for: Card.self,
inMemory: true // 内存模式,不写入磁盘
)
}
最佳实践
- 先看 “Meet SwiftData” 再看这场 Session:这场是集成实战,基础概念在前一场中讲解。
- 用 @Bindable 替代 @ObservedObject:配合 SwiftData 的 @Model 和新的 Observable 协议,数据绑定代码更少。
- 为 Preview 准备内存容器:创建 inMemory 的 ModelContainer 填充样本数据,让 Preview 能展示真实数据。
- 不要手动调用 save():SwiftData 会在合适的时机自动保存。手动调用通常是不必要的。
- ModelContainer 中只列出需要的模型类型:子视图只能操作 ModelContainer 中声明的模型类型,合理规划容器的作用范围。
还有什么值得关注
- SwiftData 支持所有 Apple 平台——Mac、iPhone、Watch、TV,一个持久化方案覆盖全部。
- Session 提到了基于文档的应用(document-based app)可以极其简单地用 SwiftData 实现,这个方向值得深入探索。
@Query支持动画配置——数据变化时的列表更新可以自动带动画,不需要手动管理。- SwiftData 与新的 Observation 框架深度整合,建议配合 “Discover Observation with SwiftUI” Session 一起理解。
- Session 提到应用可以创建多个 ModelContainer——这意味着复杂应用可以为不同功能模块设置独立的存储栈。