在创建从 V1 到 V2 的迁移状态后尝试创建 SwiftData ModelContainer 时,我的应用程序在启动时崩溃。
这是我收到的错误:
Thread 1: Fatal error: Failed to create ModelContainer for Shoe: The operation couldn’t be completed. (SwiftData.SwiftDataError error 1.)
我唯一的模型从 V1 到 V2 发生了什么变化? 我添加了一个名为
defaultRunTypes: [RunType]
的新属性,其中 RunType 是一个具有 5 种不同情况的枚举。
V1 模型只有 isDefaultShoe: Bool
属性,我想扩展该功能,为不同类型的运行提供多个默认值。
现在,这是一些代码。
这就是我创建容器的方式:
typealias Shoe = ShoesSchemaV2.Shoe
final class ShoesStore {
static let container = {
let container: ModelContainer
do {
container = try ModelContainer(for: Shoe.self, migrationPlan: ShoesMigrationPlan.self)
} catch {
fatalError("Failed to create ModelContainer for Shoe: \(error.localizedDescription)")
}
return container
}()
}
这是迁移计划。
enum ShoesMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[ShoesSchemaV1.self, ShoesSchemaV2.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2]
}
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: ShoesSchemaV1.self,
toVersion: ShoesSchemaV2.self,
willMigrate: { context in
print("Migration: willMigrae")
let v1Shoes = try? context.fetch(FetchDescriptor<ShoesSchemaV1.Shoe>())
for v1Shoe in v1Shoes ?? [] {
print("Migration: V1 Shoe: \(v1Shoe.brand) \(v1Shoe.model) - isDefaultShoe = \(v1Shoe.isDefaultShoe)")
let v2Shoe = ShoesSchemaV2.Shoe(
id: v1Shoe.id,
image: v1Shoe.image,
brand: v1Shoe.brand,
model: v1Shoe.model,
nickname: v1Shoe.nickname,
lifespanDistance: v1Shoe.lifespanDistance,
aquisitionDate: v1Shoe.aquisitionDate,
totalDistance: v1Shoe.totalDistance,
totalDuration: v1Shoe.totalDuration,
lastActivityDate: v1Shoe.lastActivityDate,
isRetired: v1Shoe.isRetired,
retireDate: v1Shoe.retireDate,
isDefaultShoe: v1Shoe.isDefaultShoe,
defaultRunTypes: v1Shoe.isDefaultShoe ? [.daily] : [], // this is the newly added property
workouts: v1Shoe.workouts,
personalBests: v1Shoe.personalBests,
totalRuns: v1Shoe.totalRuns
)
print("Migration: V2 shoe created: \(v1Shoe.brand) \(v1Shoe.model) - defaultRunTypes: \(v2Shoe.defaultRunTypes.count)")
context.insert(v2Shoe)
context.delete(v1Shoe)
}
try? context.save()
},
didMigrate: { context in
print("Migration: didMigrate")
let v2Shoes = try? context.fetch(FetchDescriptor<ShoesSchemaV2.Shoe>())
for v2Shoe in v2Shoes ?? [] {
print("Migration: V2 shoe: \(v2Shoe.brand) \(v2Shoe.model) - defaultRunTypes: \(v2Shoe.defaultRunTypes.count)")
}
}
)
}
迁移时,我有以下控制台输出。
willMigrate
正在执行,但不是didMigrate
Migration: willMigrate
Migration: V1 Shoe: Adidas Adizero Boston 12 - isDefaultShoe = true
Migration: V2 shoe created: Adidas Adizero Boston 12 - defaultRunTypes: 1
Migration: V1 Shoe: Nike Pegasus Turbo - isDefaultShoe = false
Migration: V2 shoe created: Nike Pegasus Turbo - defaultRunTypes: 0
Migration: V1 Shoe: Nike Zoomfly 5 - isDefaultShoe = false
Migration: V2 shoe created: Nike Zoomfly 5 - defaultRunTypes: 0
Migration: V1 Shoe: Nike Vaporfly 3 - isDefaultShoe = false
Migration: V2 shoe created: Nike Vaporfly 3 - defaultRunTypes: 0
Migration: V1 Shoe: Hoka Speedgoat 6 - isDefaultShoe = false
Migration: V2 shoe created: Hoka Speedgoat 6 - defaultRunTypes: 0
ShoeHealth/ShoesStore.swift:21: Fatal error: Failed to create ModelContainer for Shoe: The operation couldn’t be completed. (SwiftData.SwiftDataError error 1.)
我不知道这里发生了什么,因为这是我正在进行的第一次迁移。我创建 ModelContainer 的方式有问题吗(静态 - 我这样做是因为我也需要在
WidgetKit
中访问它)?
这就是我在 AppIntentTimelineProvider 中访问它的方式:
struct MediumShoeStatsTimelineProvider: AppIntentTimelineProvider {
let modelContext = ModelContext(ShoesStore.container)
// rest of the code, where I am using the modelContext to fetch models
}
后期编辑: 我尝试从自定义 MigrationState 更改为轻量级,并且它有效。除了事实之外,正如预期的那样,所有鞋子的
defaultRunTypes
都是空的,因为这是默认值。
所以我猜我制作自定义 MigrationState 的方式是错误的。
static let migrateV1toV2 = MigrationStage.lightweight(fromVersion: ShoesSchemaV1.self, toVersion: ShoesSchemaV2.self)
找到解决方案。不确定这是否是正确的方法,但我们开始吧:
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: ShoesSchemaV1.self,
toVersion: ShoesSchemaV2.self,
willMigrate: nil,
didMigrate: { context in
let shoes = try context.fetch(FetchDescriptor<ShoesSchemaV2.Shoe>())
for shoe in shoes {
print("Migration: \(shoe.brand) \(shoe.model)")
shoe.defaultRunTypes = shoe.isDefaultShoe ? [.daily] : []
}
try context.save()
}
)
由于我的新属性有默认值,因此我让
willMigrate
执行其操作,然后在 didMigrate
部分中为每只鞋子手动设置新属性值。
说实话,我不知道是什么原因导致了这个问题。也许
willMigrate
不允许在模型上下文中插入,我真的不知道。
我知道
willMigrate
上下文可以让您访问 V1 模型,而 didMigrate
上下文可以让您访问 V2 模型。
注意:不会接受这个答案,因为我正在等待其他解决方案(或者也许这是正确的方法)。