Wenn ich beispielsweise ohne eine Migration den Typ einer Eigenschaft von Bool in [Bool] ändere, stürzt die App ab, wenn sie versucht zu laden SwiftData, da das gespeicherte Modell nicht den richtigen Typ hat.
Aktuelles Setup
Das aktuelle @Model, das für SwiftData-Standards ziemlich einfach ist, außer dass es viel mehr Eigenschaften hat, als ich hier aufgenommen habe:
Code: Select all
@Model
final class MyData
{
var property1: Bool = true
var property2: Bool = true
var property3: Int = 1
// And many more properties...
// Default init.
init()
{
// Assign default values to properties.
self.property1 = true
self.property2 = true
self.property3 = 1
// ...
}
// Dynamic init.
init(
property1: Bool,
property2: Bool,
property3: Int
)
{
self.property1 = property1
self.property2 = property2
self.property3 = property3
// ...
}
}
Code: Select all
var sharedModelContainer: ModelContainer =
{
let schema = Schema(
[
MyData.self,
])
let modelConfiguration = ModelConfiguration(
schema: schema,
isStoredInMemoryOnly: false
)
do
{
return try ModelContainer(
for: schema,
configurations: [modelConfiguration]
)
}
catch
{
fatalError("Could not create ModelContainer: \(error)")
}
}()
@main
struct myApp: App
{
var body: some Scene
{
WindowGroup
{
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
Um mit der Implementierung der Migration zu beginnen, habe ich das Modell umbenannt, um seine Version widerzuspiegeln:
Code: Select all
@Model
final class MyDataV1
{
// No other changes compared to the code shared above.
}
Code: Select all
@Model
final class MyDataV2
{
// Changed to an array.
var property1: [Bool] = [true]
var property2: Bool = true
var property3: Int = 1
// And many more properties...
// Migration init.
init(
myDataV1: MyDataV1
)
{
// Convert to an array.
self.property1 = [myDataV1.property1]
self.property2 = myDataV1.property2
self.property3 = myDataV1.property3
// ...
}
// Default init.
init()
{
// Assign default values to properties.
self.property1 = [true]
self.property2 = true
self.property3 = 1
// ...
}
// Dynamic init.
init(
property1: [Bool],
property2: Bool,
property3: Int
)
{
self.property1 = property1
self.property2 = property2
self.property3 = property3
// ...
}
}
Code: Select all
typealias MyData = MyDataV2
Code: Select all
enum MyDataSchemaV1: VersionedSchema
{
static var versionIdentifier: Schema.Version = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type]
{
[MyDataV1.self]
}
}
enum MyDataSchemaV2: VersionedSchema
{
static var versionIdentifier: Schema.Version = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type]
{
[MyDataV2.self]
}
}
Code: Select all
enum MyDataMigrationPlan: SchemaMigrationPlan
{
static var schemas: [any VersionedSchema.Type]
{
[MyDataSchemaV1.self, MyDataSchemaV2.self]
}
static var stages: [MigrationStage]
{
[migrateFromV1ToV2]
}
private static var migrationFromV1ToV2Data: [MyDataV1] = []
static let migrateFromV1ToV2 = MigrationStage.custom(
fromVersion: MyDataSchemaV1.self,
toVersion: MyDataSchemaV2.self,
willMigrate:
{
modelContext in
let descriptor : FetchDescriptor = FetchDescriptor()
let v1Data : [MyDataV1] = try modelContext.fetch(descriptor)
migrationFromV1ToV2Data = v1Data
try modelContext.delete(model: MyDataV1.self)
try modelContext.save()
},
didMigrate:
{
modelContext in
migrationFromV1ToV2Data.forEach
{
myDataV1 in
let myDataV2 = MyDataV2(myDataV1: myDataV1)
modelContext.insert(myDataV2)
}
try modelContext.save()
}
)
}
Code: Select all
var sharedModelContainer: ModelContainer =
{
let schema = Schema(
[
MyData.self,
])
let modelConfiguration = ModelConfiguration(
schema: schema,
isStoredInMemoryOnly: false
)
do
{
return try ModelContainer(
for: schema,
migrationPlan: MyDataMigrationPlan.self,
configurations: [modelConfiguration]
)
}
catch
{
fatalError("Could not create ModelContainer: \(error)")
}
}()
Ich habe eine Situation erstellt, in der eine Instanz von MyDataV1 bereits in SwiftData gespeichert wurde, dann habe ich die oben genannten Änderungen implementiert und eine neue Version gestartet.
Ich habe Protokolle zum MigrationPlan hinzugefügt und das bestätigt willMigrate startet und endet ohne Fehler, aber die folgenden Fehler werden angezeigt, bevor didMigrate überhaupt startet, und führen zum Absturz der App:
Code: Select all
CoreData: error: Error: Persistent History (3178) has to be truncated due to the following entities being removed: (
MyDataV1
)
CoreData: warning: Warning: Dropping Indexes for Persistent History
CoreData: warning: Warning: Dropping Transactions prior to 3178 for Persistent History
CoreData: warning: Warning: Dropping Changes prior to TransactionID 3178 for Persistent History
CoreData: error: addPersistentStoreWithType:configuration:URL:options:error: returned error NSCocoaErrorDomain (134060)
CoreData: error: userInfo:
CoreData: error: NSLocalizedFailureReason : Instances of NSCloudKitMirroringDelegate are not reusable and should have a lifecycle tied to a given instance of NSPersistentStore.
CoreData: error: storeType: SQLite
CoreData: error: configuration: default
Ich dachte, das könnte daran liegen, dass ich in willMigrate Folgendes aufgerufen habe:
Code: Select all
try modelContext.delete(model: MyDataV1.self)
try modelContext.save()
Lösungsversuch 2
Diese Antwort legt nahe, dass die späteren Versionen des Schemas die vorherigen Versionen des Modells in ihr models-Array aufnehmen müssen. Daher habe ich Folgendes aktualisiert:
Code: Select all
enum MyDataSchemaV2: VersionedSchema
{
static var versionIdentifier: Schema.Version = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type]
{
[MyDataV1.self, MyDataV2.self]
}
}
Code: Select all
CoreData: error: Attempting to retrieve an NSManagedObjectModel version checksum while the model is still editable. This may result in an unstable verison checksum. Add model to NSPersistentStoreCoordinator and try again.
CoreData: debug: CoreData+CloudKit: -[PFCloudKitOptionsValidator validateOptions:andStoreOptions:error:](36): Validating options: containerIdentifier:iCloud.com.myapp.production databaseScope:Private ckAssetThresholdBytes: operationMemoryThresholdBytes: useEncryptedStorage:NO useDeviceToDeviceEncryption:NO automaticallyDownloadFileBackedFutures:NO automaticallyScheduleImportAndExportOperations:YES skipCloudKitSetup:NO preserveLegacyRecordMetadataBehavior:NO useDaemon:YES apsConnectionMachServiceName: containerProvider:
storeMonitorProvider: metricsClient: metadataPurger: scheduler: notificationListener: containerOptions: defaultOperationConfiguration: progressProvider: test_useLegacySavePolicy:YES archivingUtilities: bypassSchedulerActivityForInitialImport:NO bypassDasdRateLimiting:NO activityVouchers:()
storeOptions: {
NSInferMappingModelAutomaticallyOption = 1;
NSMigratePersistentStoresAutomaticallyOption = 1;
NSPersistentCloudKitContainerOptionsKey = "";
NSPersistentHistoryTrackingKey = 1;
NSPersistentStoreMirroringOptionsKey = {
NSPersistentStoreMirroringDelegateOptionKey = "";
};
NSPersistentStoreRemoteChangeNotificationOptionKey = 1;
NSPersistentStoreStagedMigrationManagerOptionKey = "";
}
CoreData: error: addPersistentStoreWithType:configuration:URL:options:error: returned error NSCocoaErrorDomain (134504)
CoreData: error: userInfo:
CoreData: error: NSLocalizedDescription : Cannot use staged migration with an unknown coordinator model version.
CoreData: error: storeType: SQLite
CoreData: error: configuration: default
Code: Select all
Cannot use staged migration with an unknown coordinator model version.
Versuchte Lösung 3
Dann habe ich versucht, ModelContainer zu initialisieren, indem ich ihm beide Versionen gegeben habe, nicht nur die neueste:
Code: Select all
var sharedModelContainer: ModelContainer =
{
let schema = Schema(
[
MyDataV1.self,
MyDataV2.self
])
let modelConfiguration = ModelConfiguration(
schema: schema,
isStoredInMemoryOnly: false
)
do
{
return try ModelContainer(
for: schema,
migrationPlan: MyDataMigrationPlan.self,
configurations: [modelConfiguration]
)
}
catch
{
fatalError("Could not create ModelContainer: \(error)")
}
}()
Code: Select all
try modelContext.delete(model: MyDataV1.self)
try modelContext.save()
Code: Select all
CoreData: CloudKit: CoreData+CloudKit: -[PFCloudKitStoreMonitor pfcloudstoremonitor_is_holding_your_store_open_waiting_for_cloudkit_activity_to_finish](125):
: Exporter / importer finished after 1 tries. Allowing store to deallocate.
CoreData: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _performSetupRequest:]_block_invoke(1240): : Failed to set up CloudKit integration for store:
Error Domain=NSCocoaErrorDomain Code=134060 "A Core Data error occurred." UserInfo={NSLocalizedFailureReason=The mirroring delegate could not initialize because it's store was removed from the coordinator.}
CoreData: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate recoverFromError:](2310): - Attempting recovery from error: Error Domain=NSCocoaErrorDomain Code=134060 "A Core Data error occurred." UserInfo={NSLocalizedFailureReason=The mirroring delegate could not initialize because it's store was removed from the coordinator.}
CoreData: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate recoverFromError:]_block_invoke(2329): The store was removed before the mirroring delegate could recover from an error:
Error Domain=NSCocoaErrorDomain Code=134060 "A Core Data error occurred." UserInfo={NSLocalizedFailureReason=The mirroring delegate could not initialize because it's store was removed from the coordinator.}
CoreData: CloudKit: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _finishedRequest:withResult:](3582): Finished request: 9C16248F-EDFB-46BB-ACEF-EAAC55C1CBBA with result: storeIdentifier: 2FE6E954-8AAF-4D89-8C6C-741C890ADEFC success: 0 madeChanges: 0 error: Error Domain=NSCocoaErrorDomain Code=134060 "A Core Data error occurred." UserInfo={NSLocalizedFailureReason=The mirroring delegate could not initialize because it's store was removed from the coordinator.}
CoreData: CloudKit: CoreData+CloudKit: -[NSCloudKitMirroringDelegate checkAndExecuteNextRequest](3551): : Checking for pending requests.
CoreData: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _performSetupRequest:]_block_invoke(1302): Failed to finish setup event: Error Domain=NSCocoaErrorDomain Code=134407 "Request '9C16248F-EDFB-46BB-ACEF-EAAC55C1CBBA' was cancelled because the store was removed from the coordinator." UserInfo={NSLocalizedFailureReason=Request '9C16248F-EDFB-46BB-ACEF-EAAC55C1CBBA' was cancelled because the store was removed from the coordinator.}
CoreData: debug: CoreData+CloudKit: -[NSCloudKitMirroringDelegate observeChangesForStore:inPersistentStoreCoordinator:](427): : Observing store:
BUG IN CLIENT OF CLOUDKIT: Registering a handler for a CKScheduler activity identifier that has already been registered (com.apple.coredata.cloudkit.activity.export.2FE6E954-8AAF-4D89-8C6C-741C890ADEFC).
Code: Select all
Error Domain=NSCocoaErrorDomain Code=134060 "A Core Data error occurred." UserInfo={NSLocalizedFailureReason=The mirroring delegate could not initialize because it's store was removed from the coordinator.}
Code: Select all
CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](450): Skipping migration for 'ANSCKDATABASEMETADATA' because it already has a column named 'ZLASTFETCHDATE'
Dieser Apple DTS-Ingenieur hat vor etwa drei Wochen vorgeschlagen, dass es sich bei genau diesen Fehlern, die ich in meinem Abschnitt Lösungsversuche 3 erhalte, um Framework-Fehler handelt.
Was bedeutet das also? Die SwiftData-Migration funktioniert einfach überhaupt nicht, wenn Sie auch die CloudKit-Synchronisierung aktiviert haben?
Wie kann ich das zum Laufen bringen?
https://developer.apple.com/documentati ... onedschema
https://developer.apple.com/documentati ... rationplan
Update
Dies ist ein SwiftData-Fehler. Die Migration funktioniert nicht wie beabsichtigt.
Wenn Sie dies lesen, sollten Sie zunächst Feedback bei Apple einreichen. Nur Apple kann dieses Problem beheben.
Darüber hinaus sollten Sie erwägen, Ihre SwiftData-Modelle so zu organisieren, dass keine Migrationen erforderlich sind, insbesondere keine benutzerdefinierten Migrationen.
Lightweight-Migrationen können eine große Anzahl von Szenarien bewältigen, aber auch diese sind fehleranfällig. Ich habe mehrere Berichte über Lightweight-Migrationen gesehen, die Fehler aufwiesen, bei denen eine automatische Migration andernfalls erfolgreich gewesen wäre. Bei einer automatischen Migration handelt es sich um eine Migration, die vollständig von SwiftData durchgeführt wird, wobei keine Migration explizit vom Entwickler angegeben wird, sondern andernfalls von einer einfachen Migration durchgeführt worden wäre.
Sie können auch auf die Verwendung von CoreData zurückgreifen.
Mobile version