所属专题:移动开发专题:Swift、SwiftUI 与 iOS 实践

SwiftData 数据迁移与版本升级:VersionedSchema 和自定义迁移实践

SwiftData 数据迁移与版本升级:VersionedSchema 和自定义迁移实践 - 暂无配图,技术文章默认封面

为什么要把数据模型当成公共接口

SwiftData 会根据模型生成持久化存储。开发阶段直接修改属性看起来很快,但应用升级后,旧用户磁盘上仍然是上一版 schema。如果没有迁移计划,轻则启动时报错,重则为了“修好”而删除用户数据。发布前应把模型版本、迁移方式和回滚策略当成接口一起评审。

下面用两个版本的任务模型演示增加字段的轻量迁移。V1 只有标题和完成状态,V2 增加优先级和更新时间。示例使用 iOS 17 的 VersionedSchema 与 SchemaMigrationPlan。

import SwiftData

enum TaskSchemaV1: VersionedSchema {
    static var versionIdentifier = Schema.Version(1, 0, 0)
    static var models: [any PersistentModel.Type] { [Task.self] }

    @Model
    final class Task {
        var title: String
        var isDone: Bool

        init(title: String, isDone: Bool = false) {
            self.title = title
            self.isDone = isDone
        }
    }
}

enum TaskSchemaV2: VersionedSchema {
    static var versionIdentifier = Schema.Version(2, 0, 0)
    static var models: [any PersistentModel.Type] { [Task.self] }

    @Model
    final class Task {
        var title: String
        var isDone: Bool
        var priority: Int
        var updatedAt: Date

        init(title: String, isDone: Bool = false,
             priority: Int = 0, updatedAt: Date = .now) {
            self.title = title
            self.isDone = isDone
            self.priority = priority
            self.updatedAt = updatedAt
        }
    }
}

enum TaskMigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] {
        [TaskSchemaV1.self, TaskSchemaV2.self]
    }

    static var stages: [MigrationStage] {
        [.lightweight(fromVersion: TaskSchemaV1.self,
                      toVersion: TaskSchemaV2.self)]
    }
}

let container = try ModelContainer(
    for: TaskSchemaV2.self,
    migrationPlan: TaskMigrationPlan.self
)

新增可选字段或带默认值的字段通常可以使用轻量迁移,但仍要在真机上用上一版数据验证。不要只在全新安装上运行,因为空数据库没有覆盖旧数据、索引和关系的情况。

什么时候需要自定义迁移

如果字段需要拆分、合并、重命名,或者新值必须由旧值计算出来,应使用 MigrationStage.custom。迁移阶段中通过 ModelContext 读取旧 schema 的对象,写入新 schema 能表达的值,并记录无法转换的条目。转换函数要保持幂等:迁移被中断后再次运行,不能重复追加文本或产生重复记录。大数据量迁移还要估计启动耗时,必要时先显示恢复进度,而不是让用户面对无响应页面。

迁移代码不应依赖网络、当前登录用户或不可重复的时间条件。远端配置变化可能让同一份本地数据在不同设备得到不同结果。对于删除字段,先发布能同时读写新旧格式的中间版本,再在下一版移除旧字段,通常比一次性删除更容易回滚。

容器配置和错误处理

正式应用可以为测试、预览和生产使用不同的 ModelConfiguration,但 schema 版本必须和迁移计划一致。容器创建失败时不要直接清空存储;先记录错误类型、schema 版本和设备系统版本,再提供可恢复的提示。开发环境可以删除沙盒重试,生产环境应保留故障现场并在用户同意后执行恢复方案。

使用 @Query 的视图会随着迁移后的数据变化自动刷新,但业务层不要假设查询结果一定非空。迁移完成后重新建立排序和索引相关的查询,检查旧数据的默认优先级是否符合产品规则。与 SwiftUI 状态配合的实践可参考SwiftUI 与 SwiftData:@Observable 状态管理实践,Foundation 日期和文件处理可参考Swift 与 Foundation 框架携手开发与Foundation 框架文件处理全流程指南。

上线前的验证矩阵

至少准备 V1、V2 和包含异常值的数据库副本,覆盖升级成功、迁移中断后重试、磁盘空间不足和模型关系为空四种情况。检查任务数量、完成状态、优先级、日期和排序是否保持预期。还要验证从新版本降级时的行为;如果不支持降级,应在发布渠道和应用内更新逻辑中明确限制。

迁移完成后记录 schema 版本和迁移耗时,数据本身不要写入敏感日志。移动专题移动开发专题中的 Foundation 与内存管理文章,可以帮助进一步排查大对象、线程和文件生命周期问题。把迁移当作可测试的发布步骤,远比上线后让用户用“重装应用”解决问题可靠。

分享这篇文章:

评论 (0)

请 登录 后发表评论, 还没有账户?立即注册

暂无评论,快来抢沙发吧!