SwiftData修改结构体属性后崩溃,求Schema迁移解决方案
SwiftData嵌入式Codable结构体的Schema迁移解决方案
问题根源
你的CloudInterval模型中,targetData是嵌入式Codable结构体。新增属性后,旧版本App存储的结构体数据不包含这些新字段,SwiftData默认解码逻辑无法兼容,导致启动崩溃。卸载重装会清除旧数据,所以能正常运行,但这不是用户友好的解决方案。
解决步骤
1. 给模型添加版本标识
给@Model标注添加版本号,让SwiftData识别Schema变更:
@Model(version: 2) // 初始版本为1,现在升级到2 class CloudInterval: Encodable, Comparable { var id = UUID() var name: String = "" var order: Int = 0 var targetData = TargetData() @Relationship(deleteRule: .nullify, inverse: \CloudBlock.intervals) var block: CloudBlock? init() { } // 保留原有Comparable、Encodable的实现代码 }
2. 实现Codable结构体的兼容解码
显式实现TargetData的解码初始化器,确保旧数据解码时,缺失的新属性会使用默认值填充:
struct TargetData: Codable, Equatable { // 原有属性 var minHeartRate: Int = 0 var maxHeartRate: Int = 0 var speed: Double = 0 var time: TimeInterval = 0 var distance: Double = 0 var enableTargetSpeed = false var enableTargetHeartRate = false var enableTargetTime = false var speedTolerance: Double = 0.02 // 新增属性 var newBoolProperty: Bool = false var newIntProperty: Int = 0 // 兼容旧数据的解码初始化器 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 解码原有属性,缺失时用默认值 minHeartRate = try container.decodeIfPresent(Int.self, forKey: .minHeartRate) ?? 0 maxHeartRate = try container.decodeIfPresent(Int.self, forKey: .maxHeartRate) ?? 0 speed = try container.decodeIfPresent(Double.self, forKey: .speed) ?? 0 time = try container.decodeIfPresent(TimeInterval.self, forKey: .time) ?? 0 distance = try container.decodeIfPresent(Double.self, forKey: .distance) ?? 0 enableTargetSpeed = try container.decodeIfPresent(Bool.self, forKey: .enableTargetSpeed) ?? false enableTargetHeartRate = try container.decodeIfPresent(Bool.self, forKey: .enableTargetHeartRate) ?? false enableTargetTime = try container.decodeIfPresent(Bool.self, forKey: .enableTargetTime) ?? false speedTolerance = try container.decodeIfPresent(Double.self, forKey: .speedTolerance) ?? 0.02 // 解码新增属性,旧数据中无此字段时用默认值 newBoolProperty = try container.decodeIfPresent(Bool.self, forKey: .newBoolProperty) ?? false newIntProperty = try container.decodeIfPresent(Int.self, forKey: .newIntProperty) ?? 0 } // 保留默认初始化器 init() {} // 定义所有属性的CodingKeys enum CodingKeys: String, CodingKey { case minHeartRate, maxHeartRate, speed, time, distance case enableTargetSpeed, enableTargetHeartRate, enableTargetTime case speedTolerance case newBoolProperty, newIntProperty } }
3. 启用SwiftData自动迁移
在创建ModelContainer时,确保配置支持自动迁移:
@main struct YourApp: App { let container: ModelContainer init() { do { let schema = Schema([CloudInterval.self, CloudBlock.self]) let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) // 初始化容器时启用自动迁移 container = try ModelContainer(for: schema, configurations: config) } catch { fatalError("初始化ModelContainer失败: \(error)") } } var body: some Scene { WindowGroup { ContentView() } .modelContainer(container) } }
原理说明
- 模型版本号让SwiftData检测到Schema变化,触发迁移流程。
- 显式的解码逻辑避免了Codable默认的严格解码行为,确保旧数据能正常解码为新结构体。
- 自动迁移会处理数据存储层面的适配,无需手动编写复杂的迁移脚本。
内容的提问来源于stack exchange,提问作者Raphaël Barthomeuf
相关产品推荐
相关产品推荐

