模型迁移

温馨提示:本文最后更新于2025-01-19 16:08:57,某些文章具有时效性,若有错误或已失效,请在下方留言

当你更改数据模型时,SwiftData 可以自动处理模型更新,也可以通过手动干预进行复杂的迁移。

自动处理更新迁移

以下是一些常见的小修改,SwiftData 会自动处理这些模型更新:

  1. 添加一个或多个新模型
  2. 添加具有默认值的一个或多个新属性。
  3. 重命名一个或多个属性。
  4. 从模型中删除属性。
  5. 添加或移除.externalStorage.allowsCloudEncryption 属性。
  6. 添加.unique 属性,并且该属性的所有值已经是唯一的。
  7. 调整关系中的删除规则。

重命名属性不丢失数据

以下示例定义一个 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 个步骤:

  1. 定义多个版本的数据模型。
  2. 将每个版本包装在一个符合 VersionedSchema 协议的枚举中(使用枚举是因为我们不会直接实例化它们)。
  3. 创建另一个符合 SchemaMigrationPlan 协议的枚举,用于处理模型版本间的迁移。
  4. 创建自定义的 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 属性列出了要迁移的模型版本,这里是从 StudentsSchemaV1StudentsSchemaV2stages 定义了迁移的阶段,这里只有一个阶段 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,并在迁移过程中修改数据。具体来说,我们在 studentname 属性后加上”ok!”,然后保存更改。

使用轻量级迁移

static var migrateV1toV2: MigrationStage {
    .lightweight(
        fromVersion: StudentsSchemaV1.self,
        toVersion: StudentsSchemaV2.self
    )
}

如果迁移非常简单,可以使用轻量级迁移,SwiftData 会自动处理模型字段之间的差异,而不需要手动编写数据修改逻辑。在这个例子中,migrateV1toV2 被定义为一个轻量级迁移阶段,适用于从 StudentsSchemaV1StudentsSchemaV2 的简单迁移。

总结

  1. 定义迁移计划:通过实现 schemaMigrationPlan 协议,指定需要迁移的模型版本及迁移阶段。
  2. 自定义迁移阶段:使用 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)
    }
}
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容