迁移以更改 CoreData 的配置

Migration to change the configuration of CoreData

我使用 CoreData 的 默认 配置启动了一个 macOS 项目。应用发布,部分用户开始使用。现在,我需要一些数据与 iCloud 同步,一些数据只存储在本地。如果我理解正确的话,我能做到这一点的唯一方法是创建两个不同的配置(在 CoreData 数据模型中),在每个配置中添加所需的实体,并相应地配置 NSPersistentContainer。 然而,上述方法可能会导致一些数据丢失,因为我不会再使用 Default 配置。

有什么方法可以将默认配置下保存的数据“迁移”到另一个配置?

经过反复试验,我找到了一个似乎可以完成工作的解决方案(但是,它看起来很脏)。 首先,在实例化容器时,我确保将我的 3 个 storeDescriptors 添加到 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"(或任何包含 Default 的字符串。根据我的经验实现,我得出的结论是配置名称可能因情况而异)。如果以上条件为真,创建一个新的managedObject,我把旧的所有属性复制到新的,删除旧的managed object,并保存上下文。

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' 中(取决于哪个配置包含哪个实体)。