文章目录
- 前言
- 创建模型
- 模式和版本控制
- 迈出关键的一步
- 创建迁移计划
- 创建模型容器
- 从视图中查询模型
- 从视图中访问模型上下文
- 总结
前言
在 WWDC 2023 上,Apple 宣布了一个备受期待的新持久性刷新,以一种新的框架形式出现:SwiftData。SwiftData 从 iOS 17 开始提供,允许使用 Swift 类型对应用程序的持久性数据进行建模,并以类型安全和声明性的方式执行 CRUD 操作。
在本文中,将展示如何配置 SwiftData 用于一个 SwiftUI 应用程序,并分享在这个过程中学到的一些经验。
创建模型
假设想要制作一个应用程序,允许用户保存他们访问的餐馆,并希望使用 SwiftData 在设备上持久保存这些信息。
首先,需要创建一个模型来表示餐馆的访问,通过使用 SwiftData 的 Model
宏对 Swift 类进行修饰:
DiaryEntry.swift
import SwiftData
@Model
final class DiaryEntry {
let restaurant: String
let createdAt: Date
init(restaurant: String, createdAt: Date = .now) {
self.restaurant = restaurant
self.createdAt = createdAt
}
}
该模型定义了两个属性:restaurant
表示用户访问的餐馆名称,createdAt
表示访问餐馆的日期。
正如所看到的,将现有模型转换为 SwiftData 模型非常简单,通常只需要使用 @Model
宏进行修饰即可。
模式和版本控制
如在 “使用 SwiftData 模式定义的模式” WWDC 会议中展示的那样,将每个应用程序模型的每个版本封装在其模式类型中是一个很好的实践。这是实现在不同版本的 SwiftData 模型之间无缝切换的第一步。
要创建一个模式版本,定义一个符合 VersionedSchema
协议的新类型,并通过实现 versionIdentifier
和 models
属性来进行确认:
DiaryEntrySchemaV1.swift
import SwiftData
enum DiaryEntryV1Schema: VersionedSchema {
static var versionIdentifier: String? = "v1"
static var models: [AnyPersistentModel.Type] { [DiaryEntry.self] }
@Model
final class DiaryEntry {
let restaurant: String
let createdAt: Date
init(restaurant: String, createdAt: Date = .now) {
self.restaurant = restaurant
self.createdAt = createdAt
}
}
}
可以将 versionIdentifier
属性设置为任何唯一标识模式版本的字符串。还需要将 models
属性设置为包含此模式版本的所有 SwiftData 模型的数组,包括所有关系类型。
迈出关键的一步
现在,模型已经命名空间化到模式类型中,需要在整个应用程序中使用模型类型的完全限定名称(DiaryEntryV1Schema.DiaryEntry
)。
这样做的问题是,如果需要创建模式的新版本,将需要遍历整个代码库并更新对模型类型的所有引用,以使用新版本的模式。
为了防止这种情况发生,可以创建一个名为 DiaryEntry
的类型别名,引用当前模型的版本,并在整个应用程序中使用这个新的类型别名:
DiaryEntry.swift
typealias DiaryEntry = DiaryEntryV1Schema.DiaryEntry
当需要更改模式的版本时,只需更新类型别名,而无需遍历整个代码库并更新对模型类型的所有引用。
创建迁移计划
下一步是创建一个迁移计划,告诉 SwiftData 如何从一个模式版本转换数据到另一个版本。
通过创建符合 SchemaMigrationPlan
协议的新类型来定义 SwiftData 的迁移计划:
DiaryEntryMigrationPlan.swift
import SwiftData
enum MigrationPlan: SchemaMigrationPlan {
static var schemas: [VersionedSchema.Type] {
[
DiaryEntryV1Schema.self
]
}
static var stages: [MigrationStage] {
[]
}
}
如上所示,上述迁移计划定义了一个名为 DiaryEntryV1Schema
的模式,并且没有迁移阶段。由于应用程序只有一个模式版本,因此目前不需要定义任何迁移阶段。
即使目前不需要迁移任何数据,也要提前进行所有这些设置,这样当需要时,所有配置都已准备就绪。
创建模型容器
一旦定义了模型和模式,需要创建一个模型容器,并通过 SwiftData 的 modelContainer
视图修饰符将其提供给应用程序的视图层次结构:
FoodieDiariesApp.swift
import SwiftUI
import SwiftData
@main
struct FoodieDiariesApp: App {
let container: ModelContainer
init() {
do {
container = try ModelContainer(
for: [DiaryEntry.self],
migrationPlan: MigrationPlan.self,
ModelConfiguration(for: [DiaryEntry.self])
)
} catch {
fatalError("无法初始化容器...")
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container)
}
}
与 “使用 SwiftData 模式定义的模式” WWDC 会议中显示的设置代码不同,如果要使用迁移计划,需要使用三个参数来初始化模型容器:
- 应用程序使用的 SwiftData 模型类型。
- 之前创建的迁移计划的类型。
- 用于要使用的已标记为
@Model
的类型的ModelConfiguration
实例。尽管最后一个参数看起来多余,但不传递它将导致运行时崩溃。
为了简化操作,建议创建一个第二个类型别名,用于封装当前模式类型(然后可以使用它来初始化模型容器),并修改之前创建的 DiaryEntry
类型别名以使用模式类型:
DiaryEntry.swift
typealias DiarySchema = DiaryEntryV1Schema
typealias DiaryEntry = DiarySchema.DiaryEntry
当需要更改模式的版本时,只需更新类型别名,而无需遍历整个代码库并更新对模型类型的所有引用。
从视图中查询模型
modelContainer
视图修饰符通过 SwiftUI 的环境将模型容器提供给所有子视图。SwiftData 附带了一个 @Query
属性包装器,它使用环境中的模型容器直接从视图中查询模型:
DiaryEntryListView.swift
import SwiftUI
import SwiftData
struct DiaryEntryListView: View {
@Query(sort: \.createdAt, order: .reverse) private var entries: [DiaryEntry]
var body: some View {
List {
ForEach(entries) { entry in
Text(entry.restaurant)
}
}
}
}
上述的 @Query
属性包装器还允许通过键路径以一种简洁和类型安全的方式对结果进行排序和过滤 。
从视图中访问模型上下文
由于环境中提供了模型容器,因此还可以直接从视图中访问模型上下文,以执行诸如从数据库中删除条目之类的操作:
DiaryEntryListView.swift
import SwiftUI
import SwiftData
struct DiaryEntryListView: View {
@Environment(\.modelContext) private var modelContext
@Query(sort: \.createdAt, order: .reverse) private var entries: [DiaryEntry]
var body: some View {
List {
ForEach(entries) { entry in
Text(entry.restaurant)
.swipeActions {
Button(action: {
modelContext.delete(entry)
try? modelContext.save()
}, label: { Label("Delete", systemImage: "xmark") })
}
}
}
}
总结
希望这篇文章对您有所帮助!我们会持续分享相关技术文章,敬请关注 。
特别感谢 Swift社区 编辑部的每一位编辑,感谢大家的辛苦付出,为 Swift社区 提供优质内容,为 Swift 语言的发展贡献自己的力量。