我在 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)")
}
}()
}
我找到了一种解决方法,可以防止应用程序在迁移过程中崩溃,但它会禁用 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)")
}
}()
}