SwiftData 迁移在首次运行时崩溃

问题描述 投票:0回答:1

我在 SwiftData 和迁移方面遇到问题,每当从 V2 模式转到 V3 模式时,应用程序都会在首次启动时崩溃并出现以下错误:

用于打开持久性存储的托管对象模型版本与用于创建持久性存储的版本不兼容。

第一次启动崩溃后,应用程序运行良好,并且我确认架构已按预期升级。

我希望解决这个问题,以便将来的迁移能够无缝进行。有没有人遇到过这个问题或者有任何建议来解决这个问题?

V1 架构

public enum DistanceTrackSchemaV1: VersionedSchema {
    public static var versionIdentifier = Schema.Version(1, 0, 0)

    public static var models: [any PersistentModel.Type] {
        [DistanceTrackSchemaV1.DistanceGoal.self]
    }

    @Model
    public final class DistanceGoal: Sendable {
        // swiftformat:disable all
        public let id: UUID = UUID()
        public var title: String = ""
        // swiftformat:disable all
        public var startingDate: Date = Date.distantPast
        // swiftformat:disable all
        public var endingDate: Date = Date.distantFuture
        public var distance: Double = -100
        public var distanceSoFar: Double = 0

        @Transient
        public var workouts: [DistanceWorkout] = []

        @Transient
        public var shouldRefresh: Bool = false

        public init(
            title: String,
            startingDate: Date,
            endingDate: Date,
            distance: Double
        ) {
            self.title = title
            self.startingDate = startingDate
            self.endingDate = endingDate
            self.distance = distance
        }
    }
}

V2 架构

public enum DistanceTrackSchemaV2: VersionedSchema {
    public static var versionIdentifier = Schema.Version(2, 0, 0)

    public static var models: [any PersistentModel.Type] {
        [DistanceTrackSchemaV2.DistanceGoal.self]
    }

    @Model
    public final class DistanceGoal: Sendable {
        // swiftformat:disable all
        public let id: UUID = UUID()
        public var title: String = ""
        // swiftformat:disable all
        public var startingDate: Date = Date.distantPast
        // swiftformat:disable all
        public var endingDate: Date = Date.distantFuture
        public var distance: Double = -100
        public var distanceSoFar: Double = 0
        public var unit: String = DistanceMetric.miles.rawValue

        @Transient
        public var workouts: [DistanceWorkout] = []

        @Transient
        public var shouldRefresh: Bool = false

        public init(
            title: String,
            startingDate: Date,
            endingDate: Date,
            unit: DistanceMetric,
            distance: Double
        ) {
            self.title = title
            self.startingDate = startingDate
            self.endingDate = endingDate
            self.unit = unit.rawValue
            self.distance = distance
        }
    }
}

V3 架构

public enum DistanceTrackSchemaV3: VersionedSchema {
    public static var versionIdentifier = Schema.Version(2, 1, 1)

    public static var models: [any PersistentModel.Type] {
        [DistanceTrackSchemaV3.DistanceGoal.self]
    }

    @Model
    public final class DistanceGoal: Sendable {
        // swiftformat:disable all
        public let id: UUID = UUID()
        public var title: String = ""
        // swiftformat:disable all
        public var startingDate: Date = Date.distantPast
        // swiftformat:disable all
        public var endingDate: Date = Date.distantFuture
        public var distance: Double = -100
        public var distanceSoFar: Double = 0
        public var unit: String = DistanceMetric.miles.rawValue
        public var rawWorkoutTypes: [String] = [
            WorkoutType.walking.rawValue,
            WorkoutType.running.rawValue,
            WorkoutType.wheelchairRunPace.rawValue,
            WorkoutType.wheelchairWalkPace.rawValue,
        ]

        @Transient
        public var workouts: [DistanceWorkout] = []

        public init(
            title: String,
            startingDate: Date,
            endingDate: Date,
            unit: DistanceMetric,
            distance: Double,
            workoutTypes: [WorkoutType]
        ) {
            self.title = title
            self.startingDate = startingDate
            self.endingDate = endingDate
            self.unit = unit.rawValue
            self.distance = distance
            self.rawWorkoutTypes = workoutTypes.map({$0.rawValue})
        }
    }
}

迁移计划

public typealias DistanceSchema = DistanceTrackSchemaV3
public typealias DistanceGoal = DistanceSchema.DistanceGoal

enum DistanceTrackMigrationPlan: SchemaMigrationPlan {
    static var schemas: [VersionedSchema.Type] {
        [
            DistanceTrackSchemaV1.self,
            DistanceTrackSchemaV2.self,
            DistanceTrackSchemaV3.self,
        ]
    }

    static var stages: [MigrationStage] {
        [
            migrateV1toV2,
            migrateV2toV210,
        ]
    }

    static let migrateV1toV2 = MigrationStage.lightweight(
        fromVersion: DistanceTrackSchemaV1.self,
        toVersion: DistanceTrackSchemaV2.self
    )

    static let migrateV2toV210 = MigrationStage.custom(
        fromVersion: DistanceTrackSchemaV2.self,
        toVersion: DistanceTrackSchemaV3.self
    ) { _ in } didMigrate: { context in
        let goals = try? context.fetch(
            FetchDescriptor<DistanceTrackSchemaV3.DistanceGoal>()
        )

        goals?.forEach { goal in
            goal.rawWorkoutTypes = [
                WorkoutType.walking.rawValue,
                WorkoutType.running.rawValue,
                WorkoutType.wheelchairRunPace.rawValue,
                WorkoutType.wheelchairWalkPace.rawValue,
            ]
        }

        try? context.save()
    }
}

模型容器创建

public extension ModelContainer {
    private enum Constants {
        static var AppGroup = "group.XXXXXXXXXX"
        static var CloudKitContainerName = "XXXXXXXXXX"
        static var CloudContainer = "iCloud.XXXXXXXXXX"
        static var SQLFile = "XXXXXXXXXX-Shared.sqlite"
    }

    static var DistanceTrackContainer: ModelContainer = {
        do {
            let config: ModelConfiguration = .init(
                    Constants.CloudKitContainerName,
                    groupContainer: .identifier(Constants.AppGroup),
                    cloudKitDatabase: .private(Constants.CloudContainer)
                )

            let container = try ModelContainer(
                for: DistanceGoal.self,
                migrationPlan: DistanceTrackMigrationPlan.self,
                configurations: config
            )

            return container

        } catch {
            fatalError("Failed to configure SwiftData container. Error: \(error)")
        }
    }()
}
swift migration swift-data
1个回答
0
投票

我找到了一种解决方法,可以防止应用程序在迁移过程中崩溃,但它会禁用 iCloud 同步,直到应用程序重新启动为止。我不喜欢这个解决方案,我宁愿找到一些不会禁用 iCloud 同步的解决方案。

public extension ModelContainer {
    private enum Constants {
        static var AppGroup = "group.XXXXXXXXXX"
        static var CloudKitContainerName = "XXXXXXXXXX"
        static var CloudContainer = "iCloud.XXXXXXXXXX"
        static var SQLFile = "XXXXXXXXXX-Shared.sqlite"
    }
    
    static var DistanceTrackContainer: ModelContainer = {
        do {
            let cloudConfig: ModelConfiguration = .init(
                Constants.CloudKitContainerName,
                groupContainer: .identifier(Constants.AppGroup),
                cloudKitDatabase: .private(Constants.CloudContainer)
            )
            
            let localConfig: ModelConfiguration = .init(
                Constants.CloudKitContainerName,
                groupContainer: .identifier(Constants.AppGroup),
                cloudKitDatabase: .none
            )
            
            let container: ModelContainer
            if let iCloudContainer = try? ModelContainer(
                for: DistanceGoal.self,
                migrationPlan: DistanceTrackMigrationPlan.self,
                configurations: cloudConfig
            ) {
                container = iCloudContainer
            } else {
                container = try ModelContainer(
                    for: DistanceGoal.self,
                    migrationPlan: DistanceTrackMigrationPlan.self,
                    configurations: localConfig
                )
            }
            
            return container
            
        } catch {
            fatalError("Failed to configure SwiftData container. Error: \(error)")
        }
    }()
}
© www.soinside.com 2019 - 2024. All rights reserved.