2025-01-19 16:08:57
,某些文章具有时效性,若有错误或已失效,请在下方留言。当你更改数据模型时,SwiftData 可以自动处理模型更新,也可以通过手动干预进行复杂的迁移。
自动处理更新迁移
以下是一些常见的小修改,SwiftData 会自动处理这些模型更新:
- 添加一个或多个新模型。
- 添加具有默认值的一个或多个新属性。
- 重命名一个或多个属性。
- 从模型中删除属性。
- 添加或移除
.externalStorage
或.allowsCloudEncryption
属性。 - 添加
.unique
属性,并且该属性的所有值已经是唯一的。 - 调整关系中的删除规则。
重命名属性不丢失数据
以下示例定义一个 Student
的模型,通过 @Attribute
宏来告诉 SwiftData 的原始属性名称,以便 SwiftData 可以代表你从旧的名称迁移到新的名称。
@Model class Student {
var name: String
init(name: String) {
self.name = name
}
}
操作示例如下:
@Model class Student {
var name: String
@Attribute(originalName: "name") var fullName: String
init(name: String) {
self.fullName = name
}
}
这告诉 SwiftData 以后使用 fullName
属性,但如果旧模型版本中使用的是 name
,它会自动升级并重命名为 fullName
。
如果更改属性名称时未使用 @Attribute(originalName:)
,SwiftData 会将旧属性与新属性视为不同,导致无法迁移数据。如果要丢弃旧数据并创建新属性可以这样做,但若要避免数据丢失,请使用 @Attribute
。
复杂迁移
当自动迁移无法完成时,我们需要提供自定义代码控制迁移。例如,数据去重或枚举类型的属性分类无法通过过滤获取时,必须手动执行复杂迁移,明确告知 SwiftData 旧版和新版模型,并提供自定义逻辑处理。
这个过程需要 4 个步骤:
- 定义多个版本的数据模型。
- 将每个版本包装在一个符合
VersionedSchema
协议的枚举中(使用枚举是因为我们不会直接实例化它们)。 - 创建另一个符合
SchemaMigrationPlan
协议的枚举,用于处理模型版本间的迁移。 - 创建自定义的
ModelContainer
配置,按需使用迁移计划。
创建版本数据模型
在这个步骤中,我们将定义数据模型的多个版本,并使用 VersionedSchema
协议将其封装在枚举中。
创建 StudentsSchemaV1 模型
enum StudentsSchemaV1: VersionedSchema {
static var versionIdentifier = Schema.Version(1, 0, 0) // 定义模型的版本
static var models: [any PersistentModel.Type] {
[Student.self] // 包含的模型类
}
@Model class Student {
var name: String // 定义学生的属性
init(name: String, age: Int) {
self.name = name
}
}
}
在 StudentsSchemaV1
中,我们定义了第一个版本的 Student
模型,包含 name
属性。
创建 StudentsSchemaV2 模型
enum StudentsSchemaV2: VersionedSchema {
static var versionIdentifier = Schema.Version(2, 0, 0) // 定义模型的版本
static var models: [any PersistentModel.Type] {
[Student.self] // 包含的模型类
}
@Model class Student {
@Attribute(.unique) var name: String // 在第二版中,`name` 属性变为唯一
init(name: String, age: Int) {
self.name = name
}
}
}
在 studentsSchemaV2
中,name
属性被添加了 .unique
特性,表示它是唯一的。这是版本 2的更新。
使用别名引用模型
typealias Student = StudentsSchemaV2.Student
你可以使用 typealias
为特定模型创建别名,方便后续代码使用。例如, Student
可以是StudentsSchemaV2.student
的简化名称。
创建迁移计划
在这一部分,我们将创建迁移计划,用于处理不同版本之间的数据迁移。创建 StudentsMigrationPlan
迁移计划
enum StudentsMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[StudentsSchemaV1.self, StudentsSchemaV2.self] // 定义迁移的模型版本
}
// 定义迁移的各个阶段
static var stages: [MigrationStage] {
[migrateV1toV2, migrateV2toV3] // 可以定义多个阶段
}
}
在 StudentsMigrationPlan
中, schemas
属性列出了要迁移的模型版本,这里是从 StudentsSchemaV1
到 StudentsSchemaV2
。 stages
定义了迁移的阶段,这里只有一个阶段 migrateV1toV2
。定义 migrateV1toV2
迁移阶段:
let migrateV1toV2 = MigrationStage.custom(
fromVersion: StudentsSchemaV1.self,
toVersion: StudentsSchemaV2.self,
willMigrate: nil
) { context in
// 在迁移过程中修改数据
let paste = try context.fetch(FetchDescriptor<StudentsSchemaV2.Student>())
paste.forEach { item in
item.name = "\(item.name) ok!" // 更新数据
}
try context.save() // 保存更改
}
migrateV1toV2
是一个自定义的迁移阶段。这里我们指定了从 StudentsSchemaV1
迁移到 StudentsSchemaV2
,并在迁移过程中修改数据。具体来说,我们在 student
的 name
属性后加上”ok!”,然后保存更改。
使用轻量级迁移
static var migrateV1toV2: MigrationStage {
.lightweight(
fromVersion: StudentsSchemaV1.self,
toVersion: StudentsSchemaV2.self
)
}
如果迁移非常简单,可以使用轻量级迁移,SwiftData 会自动处理模型字段之间的差异,而不需要手动编写数据修改逻辑。在这个例子中,migrateV1toV2
被定义为一个轻量级迁移阶段,适用于从 StudentsSchemaV1
到 StudentsSchemaV2
的简单迁移。
总结
- 定义迁移计划:通过实现
schemaMigrationPlan
协议,指定需要迁移的模型版本及迁移阶段。 - 自定义迁移阶段:使用
Migrationstage.custom
来执行复杂的数据迁移逻辑,或者使用轻量级迁移简化迁移过程。
使用迁移计划
在这个部分,我们将展示如何在 ModelContainer
中使用迁移计划来处理模型的迁移。创建数据库服务类:
typealias Student = StudentsSchemaV2.Student
class DatabaseService {
static var shared = DatabaseService()
var container: ModelContainer
var context: ModelContext
init() {
do {
// 定义数据存储的位置
let storeURL = URL.documentsDirectory.appending(path: "students.sqlite")
// 定义模型的 schema,包含学生模型
let schema = Schema([ Student.self ])
// 配置数据库模型
let modelConfiguration = ModelConfiguration(
schema: schema,
url: storeURL
)
// 创建 ModelContainer,传入迁移计划
container = try ModelContainer(
for: schema,
migrationPlan: StudentsMigrationPlan.self, // 使用迁移计划
configurations: [modelConfiguration]
)
// 创建上下文
context = ModelContext(container)
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}
}
在 DatabaseService
类中,我们首先指定数据库存储的路径 students.sqlite
,然后定义了模型的 schema
,并为数据库配置了模型。通过使用 ModelContainer
初始化时,我们将迁移计划 studentsMigrationPlan
传入,这样 SwiftData 会根据迁移计划处理不同版本之间的数据迁移。
创建 DatabaseService
实例来管理与数据库的交互,并通过 modelContainer
修饰符将 DatabaseService
的 e 绑定到 SwiftUI 视图层次结构中:
import SwiftUI
import SwiftData
@main
struct SwiftDataExamplesApp: App {
var databaseService: DatabaseService = DatabaseService()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(self.databaseService.container)
}
}
暂无评论内容