Например, без миграции, если я изменю тип свойства с Bool на [Bool], приложение выйдет из строя при попытке загрузки SwiftData, поскольку сохраненная модель имеет неправильный тип.
Текущая настройка
Текущая @Model, которая довольно проста по стандартам SwiftData, за исключением того, что у нее гораздо больше свойств, чем я включил здесь:
Код: Выделить всё
@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
// ...
}
}
Код: Выделить всё
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)
}
}
Чтобы начать реализацию миграции, я переименовал модель, чтобы отразить ее версию:
Код: Выделить всё
@Model
final class MyDataV1
{
// No other changes compared to the code shared above.
}
Код: Выделить всё
@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
// ...
}
}
Код: Выделить всё
typealias MyData = MyDataV2
Код: Выделить всё
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]
}
}
Код: Выделить всё
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()
}
)
}
Код: Выделить всё
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)")
}
}()
Я создал ситуацию, в которой экземпляр MyDataV1 уже был сохранен в SwiftData, затем я реализовал указанные выше изменения и запустил новую версию.
Я добавил журналы в MigrationPlan и подтвердил, что willMigrate запускается и завершается без каких-либо ошибок, но приведенные ниже ошибки появляются еще до запуска DidMigrate и приводят к сбою приложения:
Код: Выделить всё
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
Я подумал, что это может быть потому, что в willMigrate я вызывал следующее:
Код: Выделить всё
try modelContext.delete(model: MyDataV1.self)
try modelContext.save()
Попытка решения 2
Этот ответ предполагает, что более поздние версии схемы должны включать предыдущие версии модели в массив моделей. Итак, я обновил следующее:
Код: Выделить всё
enum MyDataSchemaV2: VersionedSchema
{
static var versionIdentifier: Schema.Version = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type]
{
[MyDataV1.self, MyDataV2.self]
}
}
Код: Выделить всё
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
Код: Выделить всё
Cannot use staged migration with an unknown coordinator model version.
Попытка решения 3
Затем я попробовал инициализировать ModelContainer, задав ему обе версии, а не только самую последнюю:
Код: Выделить всё
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)")
}
}()
Код: Выделить всё
try modelContext.delete(model: MyDataV1.self)
try modelContext.save()
Код: Выделить всё
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).
Код: Выделить всё
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: debug: CoreData+CloudKit: -[PFCloudKitMetadataModelMigrator calculateMigrationStepsWithConnection:error:](450): Skipping migration for 'ANSCKDATABASEMETADATA' because it already has a column named 'ZLASTFETCHDATE'
Инженер Apple DTS около 3 недель назад предположил, что именно эти ошибки, которые я получаю в разделе Попытка решения 3, являются ошибками платформы.
Итак, что это значит? Миграция SwiftData просто не работает вообще, если вы также включили синхронизацию CloudKit?
Как мне заставить это работать?
https://developer.apple.com/documentati ... onedschema
https://developer.apple.com/documentati ... rationplan
Обновление
Это ошибка SwiftData. Миграция не работает должным образом.
Если вы читаете это, первое, что вам следует сделать, — это отправить отзыв в Apple. Только Apple может это исправить.
Кроме этого, рассмотрите возможность организации своих моделей SwiftData таким образом, чтобы не требовалось никаких миграций, особенно пользовательских.
Облегченные миграции могут обрабатывать большое количество сценариев, но даже они подвержены ошибкам. Я видел несколько учетных записей облегченных миграций с ошибками, в которых в противном случае автоматическая миграция была бы успешной. Автоматическая миграция полностью выполняется SwiftData, при этом разработчик явно не указывает миграцию, но в противном случае она выполнялась бы с помощью облегченной миграции.
Вы также можете вернуться к использованию CoreData.
Подробнее здесь: https://stackoverflow.com/questions/795 ... -swiftdata
Мобильная версия