Миграция для изменения конфигурации основных данных

#swift #core-data #core-data-migration

Вопрос:

Я начал проект macOS, используя конфигурацию CoreData по умолчанию. Приложение было выпущено, и некоторые пользователи начали им пользоваться. Теперь мне нужно, чтобы некоторые данные были синхронизированы с iCloud, а некоторые данные хранились только локально. Если я правильно понимаю, единственный способ добиться этого-создать две разные конфигурации (в модели данных CoreData), добавить необходимые сущности в каждую конфигурацию и соответствующим образом настроить NSPersistentContainer. Однако описанный выше метод может привести к некоторой потере данных, так как я больше не буду использовать конфигурацию по умолчанию.

Есть ли какой-либо способ «перенести» данные, сохраненные в конфигурации по умолчанию, в другую конфигурацию?

Ответ №1:

После некоторых проб и ошибок я нашел решение, которое, похоже, делает работу (однако, оно кажется грязным). Во-первых, при создании экземпляра контейнера я обязательно добавляю в него 3 описателя хранилища persistentStoreDescriptions (каждый из которых представляет схему)

 let defaultDirectoryURL = NSPersistentContainer.defaultDirectoryURL()
var persistentStoreDescriptions: [NSPersistentStoreDescription] = []

let localStoreLocation = defaultDirectoryURL.appendingPathComponent("Local.sqlite")
let localStoreDescription = NSPersistentStoreDescription(url: localStoreLocation)
localStoreDescription.cloudKitContainerOptions = nil
localStoreDescription.configuration = "Local"
persistentStoreDescriptions.append(localStoreDescription)

let cloudStoreLocation = defaultDirectoryURL.appendingPathComponent("Cloud.sqlite")
let cloudStoreDescription = NSPersistentStoreDescription(url: cloudStoreLocation)
cloudStoreDescription.configuration = "iCloud"
cloudStoreDescription.cloudKitContainerOptions = "iCloud.com.xxx.yyy"

persistentStoreDescriptions.append(cloudStoreDescription)

let defaultStoreLocation = defaultDirectoryURL.appendingPathComponent("Default.sqlite")
let defaultStoreDescription = NSPersistentStoreDescription(url: defaultStoreLocation)
defaultStoreDescription.cloudKitContainerOptions = nil
defaultStoreDescription.configuration = "Default"
persistentStoreDescriptions.append(defaultStoreDescription)

container.persistentStoreDescriptions = persistentStoreDescriptions       
 

Примечание: Одна важная вещь-убедиться, что NSPersistentStoreDescription конфигурация по умолчанию добавлена последней.

Во-вторых, я всегда думал, что все данные, сохраненные в основных данных, проверяют, есть ли managedObject.objectID.persistentStore?.configurationName "Default" (или любая строка, содержащая значение по умолчанию. С моей эмпирической реализацией я пришел к выводу, что имя конфигурации может отличаться от случая к случаю). Если приведенное выше условие верно, создайте новый управляемый объект, я скопирую все свойства из старого в новый, удалю старый управляемый объект и сохраню контекст.

 for oldManagedObject in managedObjectRepository.getAll() {
    guard let configurationName = oldManagedObject.objectID.persistentStore?.configurationName else {
        continue
    }
    
    if (configurationName == "Default") {
        let newManagedObject = managedObjectRepository.newManagedObject()
        
        newManagedObject.uuid = oldManagedObject.uuid
        newManagedObject.createDate = oldManagedObject.createDate
        ......

        managedObjectRepository.delete(item: oldManagedObject)        
        managedObjectRepository.saveContext()
    }
}
 

С помощью этой реализации старые данные, которые ранее были сохранены, Default.sqlite теперь сохраняются в Local.sqlite или в «Cloud.sqlite» (в зависимости от того, какая конфигурация содержит какой объект).