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

SwiftData实体名称修改与重量级迁移实现方法咨询

问题背景

我正在从CoreData迁移到SwiftData的项目中,已将CoreData模型(Event、EventDate、Lecture)通过@Model宏转换为SwiftData模型(V3Schema),代码如下:

enum V3Schema: VersionedSchema {
    static var models: [any PersistentModel.Type] = [Event.self, Lecture.self]
    
    static var versionIdentifier = Schema.Version(3, 5, 2)

    @Model public class Event {
        var date: Date?
        var memo: String?
        var status: Bool? = false
        var title: String?
        var lecture: Lecture?

        public init() { }
        
    }

    @Model public class Lecture {
        var color: String?
        var room: String?
        var title: String?
        @Relationship(deleteRule: .cascade, inverse: \Event.lecture) var events: [Event]?
        @Relationship(deleteRule: .cascade, inverse: \LectureTime.lecture) var times: [LectureTime]?
        

        public init() { }
        
    }
}

我希望在新版本(V4Schema)中修改实体名称,对应代码如下:

enum V4Schema: VersionedSchema {
    static var models: [any PersistentModel.Type] = [LectureModel.self, EventModel.self]
    
    static var versionIdentifier = Schema.Version(4, 0, 0)

    @Model class EventModel {
        var title: String = ""
        var status: EventStatus = EventStatus.incomplete
        var memo: String?
        var deadline: Deadline?
        var lecture: LectureModel?
        
        struct Deadline: Codable, Comparable {
            static func < (lhs: V4Schema.EventModel.Deadline, rhs: V4Schema.EventModel.Deadline) -> Bool {
                lhs.startAt < rhs.startAt
            }
            
            var startAt: Date
            var endAt: Date
            var isTimeRequired: Bool
        }
        
        enum EventStatus: Int, Codable {
            case incomplete = 0
            case complete = 1
        }
        
        init(eventID: String, title: String, memo: String? = nil, date: Deadline? = nil, status: EventStatus = .incomplete) {
            self.eventID = eventID
            self.title = title
            self.memo = memo
            self.deadline = date
            self.date = deadline?.startAt
            self.status = status
        }
        
    }
}

我编写了迁移计划处理模型变更,但该计划未被触发(日志未输出),代码如下:

enum ModelMigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] = [V3Schema.self, V4Schema.self]
    static var stages: [MigrationStage] = [migrateV3ToV4]
    
    static let migrateV3ToV4 = MigrationStage.custom(fromVersion: V3Schema.self,
                                                     toVersion: V4Schema.self,
                                                     willMigrate: { _ in 
    Log.debug(message: "Migration Start")
    // create new EventModel in V4Schema using Event in V3Schema.
},
                                                     didMigrate: { _ in Log.debug(message: "Migration Complete") })
    
}

现咨询以下两个问题:

  1. 如何在SwiftData中修改实体名称?
  2. 如何在SwiftData中使用重量级迁移?

问题解答

1. SwiftData中修改实体名称的方法

SwiftData默认用类名作为实体名,修改实体名称需要显式指定新旧实体的映射关系,确保迁移时能识别对应关系:

核心操作

在新的@Model类上,通过originalName参数绑定旧实体的名称,同时保持新类名作为新实体名:

enum V4Schema: VersionedSchema {
    static var models: [any PersistentModel.Type] = [LectureModel.self, EventModel.self]
    static var versionIdentifier = Schema.Version(4, 0, 0)

    // 绑定旧实体名"Event",新实体名为"EventModel"
    @Model(originalName: "Event") 
    class EventModel {
        var title: String = ""
        var status: EventStatus = EventStatus.incomplete
        var memo: String?
        var deadline: Deadline?
        var lecture: LectureModel?
        
        struct Deadline: Codable, Comparable {
            static func < (lhs: V4Schema.EventModel.Deadline, rhs: V4Schema.EventModel.Deadline) -> Bool {
                lhs.startAt < rhs.startAt
            }
            
            var startAt: Date
            var endAt: Date
            var isTimeRequired: Bool
        }
        
        enum EventStatus: Int, Codable {
            case incomplete = 0
            case complete = 1
        }
        
        init(title: String, memo: String? = nil, deadline: Deadline? = nil, status: EventStatus = .incomplete, lecture: LectureModel? = nil) {
            self.title = title
            self.memo = memo
            self.deadline = deadline
            self.status = status
            self.lecture = lecture
        }
    }

    // 绑定旧实体名"Lecture",新实体名为"LectureModel"
    @Model(originalName: "Lecture")
    class LectureModel {
        var color: String?
        var room: String?
        var title: String?
        @Relationship(deleteRule: .cascade, inverse: \EventModel.lecture) var events: [EventModel]?
        
        init(color: String? = nil, room: String? = nil, title: String? = nil) {
            self.color = color
            self.room = room
            self.title = title
        }
    }
}

2. SwiftData中使用重量级迁移(自定义迁移)的正确方式

你的迁移计划未触发,通常是因为未关联到ModelContainer、版本号逻辑错误或迁移逻辑缺失,以下是完整实现步骤:

步骤1:将迁移计划关联到ModelContainer

初始化ModelContainer时必须指定migrationPlan,否则迁移逻辑不会被执行:

let container: ModelContainer
do {
    container = try ModelContainer(
        for: V4Schema.self,
        migrationPlan: ModelMigrationPlan.self
    )
} catch {
    fatalError("Failed to create ModelContainer: \(error)")
}

步骤2:实现完整的自定义迁移逻辑

在willMigrate闭包中手动处理旧数据到新实体的转换,包括属性映射、关联关系处理,并保存上下文:

enum ModelMigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] = [V3Schema.self, V4Schema.self]
    static var stages: [MigrationStage] = [migrateV3ToV4]
    
    static let migrateV3ToV4 = MigrationStage.custom(
        fromVersion: V3Schema.self,
        toVersion: V4Schema.self,
        willMigrate: { context in
            Log.debug(message: "Migration Start")
            
            // 读取旧版本的Event数据
            guard let oldEvents = try? context.fetch(FetchDescriptor<V3Schema.Event>()) else { return }
            
            for oldEvent in oldEvents {
                // 转换状态:旧Bool? 转为 EventStatus枚举
                let status: EventStatus = (oldEvent.status ?? false) ? .complete : .incomplete
                
                // 转换Deadline:旧Date? 转为自定义Deadline结构(根据业务逻辑补全默认值)
                var deadline: EventModel.Deadline?
                if let date = oldEvent.date {
                    deadline = EventModel.Deadline(
                        startAt: date,
                        endAt: Calendar.current.date(byAdding: .hour, value: 1, to: date) ?? date,
                        isTimeRequired: false
                    )
                }
                
                // 转换关联的Lecture实体
                let newLecture = oldEvent.lecture.map { oldLecture in
                    let lecture = LectureModel(
                        color: oldLecture.color,
                        room: oldLecture.room,
                        title: oldLecture.title
                    )
                    lecture.events = [EventModel]()
                    return lecture
                }
                
                // 创建新的EventModel并添加到上下文
                let newEvent = EventModel(
                    title: oldEvent.title ?? "",
                    memo: oldEvent.memo,
                    deadline: deadline,
                    status: status,
                    lecture: newLecture
                )
                context.insert(newEvent)
                
                // 关联Lecture和Event的关系
                newLecture?.events?.append(newEvent)
            }
            
            // 保存迁移上下文
            try? context.save()
        },
        didMigrate: { _ in
            Log.debug(message: "Migration Complete")
        }
    )
}

步骤3:验证迁移触发条件

  • 确保V4Schema的版本号4.0.0高于V3Schema的3.5.2
  • 测试时需先安装旧版本App,再升级到新版本(直接安装新版本不会触发迁移)
  • 检查日志工具是否正常工作,确保Log.debug能输出内容

内容的提问来源于stack exchange,提问作者LeeProgrammer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 06:19:51