Я пытаюсь выполнить пользовательскую миграцию для своих моделей данных SwiftData, поскольку модель включает в себя пользовательские перечисления и структуру. (Составной атрибут) В новой версии добавлено несколько дополнительных перечислений.
По какой-то причине миграция успешно выполняется на моем iPad, но пропускается в симуляторах и на моем iPhone. Несмотря на установку тех же версий из Xcode или Testflight.
Если миграция пропущена, вновь добавленные перечисления не инициализируются (несмотря на значения по умолчанию), что приводит к следующей ошибке:
CoreData: warning: validation recovery attempt FAILED with Error Domain=NSCocoaErrorDomain Code=1560 "Multiple validation errors occurred." UserInfo = {NSDetailedErrors=(
"Error Domain=NSCocoaErrorDomain Code=1570 \"%{PROPERTY}@ is a required value.\"
Я уже проверил контрольные суммы версий моделей данных, и они всегда совпадают с версией V1. С помощью точек останова я также проверил, что на всех устройствах и симуляторах MigrationPlan загружается, но запускается только на iPad. Протестировал на iOS 17.4 и 17.5.
План миграции
enum DataMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[DataSchemaV1.self, DataSchemaV2.self]
}
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: DataSchemaV1.self,
toVersion: DataSchemaV2.self,
willMigrate: { context in
print("will migrate")
}, didMigrate: { context in
print("did migrate")
// … setting values for new properties
}
)
static var stages: [MigrationStage] {
[migrateV1toV2]
}
}
МодельКонтейнер
let modelConfiguration = ModelConfiguration(isStoredInMemoryOnly: false)
sharedModelContainer = try ModelContainer (
for: DataSchemaV2.FilterGroup.self,
migrationPlan: DataMigrationPlan.self,
configurations: modelConfiguration
)
Схема данныхV1
enum DataSchemaV1: VersionedSchema {
static var versionIdentifier = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] {
[DataSchemaV1.FilterGroup.self]
}
@Model
final class FilterGroup: Identifiable {
public let id: String = UUID().uuidString
var timestamp: Date = Date.now
var theme: WidgetTheme = WidgetTheme.calendar // string enum
var myStruct: [MyStruct] = []
}
static var sampleData: FilterGroup {
return FilterGroup(...)
}
struct MyStruct: Codable, Identifiable {
var id: UUID = UUID()
var someEnum: SomeEnum = SomeEnum.something
...
}
}
Схема данныхV2
enum DataSchemaV2: VersionedSchema {
static var versionIdentifier = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type] {
[DataSchemaV2.FilterGroup.self]
}
@Model
final class FilterGroup: Identifiable {
// ... same as v1
var newlyAddedEnum: NewEnum = NewEnum.something
}
// ...
}
Есть идеи, почему миграция запускается только на некоторых устройствах?
@JoakimDanielson Я просто удалил это из примера, чтобы сделать его минимальным.
Я подозреваю, что это связано с тем, что вы используете замыкание didMigrate вместо willMigrate в функции миграции. Вам необходимо установить значение по умолчанию для существующих объектов до миграции, а не после.
@JoakimDanielson Проблема в том, что пропущены и willMigrate, и didMigrate. Если миграция не пропущена, установка значений по умолчанию для нового объекта в didMigrate является правильной. Значения для существующих объектов уже установлены, поэтому их не нужно задавать снова в willMigrate.
Извините, похоже, я частично неправильно понял вопрос. И теперь меня немного смущает will/didMigrate, я надеюсь, что они скоро добавят более подробную документацию.





Оказывается, проблема заключалась в том, что я также создал ModelContainer в своем виджете. Там я не добавил MigrationPlan. Это могло привести к состоянию гонки, когда в некоторых случаях ModelContainer сначала переносился моим основным приложением с помощью customMigrationPlan, а в некоторых случаях виджетом с использованием облегченной миграции.
После добавления MigrationPlan в виджет миграция выполняется как положено.
Как отладить
Если вы находитесь в похожей ситуации, это помогло мне наконец отладить ее:
ModelContainer и затем получите URL, используя этот кодlet url = myModelContainer.mainContext.configurations.first?.url.path(percentEncoded: false)
ATransactionString и вы увидите все произошедшие миграции. В моем случае я нашел «com.apple.coredata.schemamigrator: облегченная миграция…» и указал цель моего виджета.
Но вы не устанавливаете новое свойство в своей функции миграции?