Die benutzerdefinierte SwiftData-Migration schlägt immer fehl – ​​Möglicher Fehler in SwiftDataIOS

Programmierung für iOS
Anonymous
 Die benutzerdefinierte SwiftData-Migration schlägt immer fehl – ​​Möglicher Fehler in SwiftData

Post by Anonymous »

Ich verwende SwiftData in meiner SwiftUI-iOS-App und muss vor dem Start einen Migrationsplan implementieren, damit alles später reibungslos läuft, wenn ich Änderungen an den gespeicherten Daten vornehmen muss.
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
// ...
}
}
Erstellung des ModelContainers:

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)
}
}
Migrationsversuch
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.
}
Dann habe ich MyDataV2 definiert, um das neue Format der Daten darzustellen. Das Einzige, was sich geändert hat, ist der Typ von Eigenschaft1:

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
// ...
}
}
Ich habe einen Typalias definiert, um die aktuelle Version widerzuspiegeln, sodass andere Teile meiner App ihre Referenzen nicht ständig aktualisieren müssen:

Code: Select all

typealias MyData = MyDataV2
Ich habe zwei VersionedSchema-Instanzen definiert, eine zur Darstellung von V1 und eine andere zur Darstellung von V2:

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]
}
}
Ich habe einen SchemaMigrationPlan definiert, um die Migration von MyDataV1 nach MyDataV2 abzuwickeln:

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()
}
)
}
Und schließlich habe ich die ModelContainer-Initialisierung aktualisiert, um den Migrationsplan einzuschließen:

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)")
}
}()
Migrationsfehler
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
Lösungsversuch 1
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()
Also habe ich versucht, diese Zeilen zu entfernen, aber es verursachte immer noch den gleichen Fehler. Diese Antwort hat die gleiche Implementierung mit angeblich keinen Fehlern, daher ist es nicht verwunderlich, dass dies nicht die Ursache des Problems war.

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]
}
}
Dies verursacht nicht den gleichen Fehler, stürzt aber dennoch mit der folgenden Fehlermeldung ab, bevor willMigrate überhaupt beginnt:

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
Wobei ich denke, dass diese Zeile der relevante Fehler ist:

Code: Select all

Cannot use staged migration with an unknown coordinator model version.
Diese Antwort legt nahe, dass das Problem darin besteht, dass SwiftData nicht weiß, von welchem ​​Modell aus mit der Migration begonnen werden soll. Ich weiß nicht, wie das der Fall sein könnte, oder wenn es der Fall ist, wie ich es beheben könnte.

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)")
}
}()
Während diese beiden Zeilen immer noch von willMigrate ausgeschlossen werden:

Code: Select all

try modelContext.delete(model: MyDataV1.self)
try modelContext.save()
Dieses Mal wurde willMigrate beendet (wie beim ersten Versuch), stürzte aber immer noch mit einem neuen Fehler ab, unmittelbar nachdem willMigrate fertig war:

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).
Wo dies der relevante Fehler sein könnte:

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.}
Und ungefähr 15 Zeilen wie diese wurden während willMigrate protokolliert:

Code: Select all

CoreData: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](450): Skipping migration for 'ANSCKDATABASEMETADATA' because it already has a column named 'ZLASTFETCHDATE'
Ist es ein CoreData/SwiftData-Fehler?
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.

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post