You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 06:16:10