肇鑫的技术博客

业精于勤,荒于嬉

Core Data融合升级时可能会遇到的问题

更新1

有关local store和cloud store的补充说明。

苹果文档讲述了Core Data融合的基本的方法。不过,它讲述的比较简略。代码好多部分都是占位符,需要自己填写。此外,苹果的融合,默认的单机模式,也就是没有使用CloudKit的同步的情况。如果你使用CloudKit,就会遇到苹果没有提到的情况。

Core Data Model Versioning and Data Migration

融合升级的步骤

融合升级的基本步骤:

  1. 融合
  2. 打开新数据库

不过我更建议:

  1. 查询是否能够融合
  2. 融合
  3. 打开新数据库

下面的文章基于后者。

1. 查询是否能够融合

private func canMigration() -> Bool {
    let sourceURL = Bundle.main.url(forResource: "Model", withExtension: "mom", subdirectory: "Model.momd")
    let destinationURL = Bundle.main.url(forResource: "Model 2", withExtension: "mom", subdirectory: "Model.momd")
    let sourceModel = NSManagedObjectModel(contentsOf: sourceURL!)
    let destinationModel = NSManagedObjectModel(contentsOf: destinationURL!)
    
    if let _ = try? NSMappingModel.inferredMappingModel(forSourceModel: sourceModel!, destinationModel: destinationModel!)  {
        return true
    }
    
    return false
}

注意:在Xcode中,创建模型的文件扩展名分别是“xcdatamodeld”和“xcdatamodel”,但是生成应用之后,它们的扩展名变成了“momd”和“mom”,并且前者变成了独立的文件夹。

xcode_model

这一步的作用是,提前知晓能否转换。如果这一步都不成,就不用执行下一步了。如果直接执行下一步,那么两部的错误可能会同时呈现,增加调试的难度。

2. 融合

private func migragtionFromModel2Model2() {
    let container = NSPersistentContainer(name: "Model")
    let moc = container.viewContext
    let applicationSupportFolderURL = try? FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
#if targetEnvironment(macCatalyst)
    let storeURL = URL(fileURLWithPath: "Photo Organizer/Model.sqlite", isDirectory: false, relativeTo: applicationSupportFolderURL)
#else
    let storeURL = URL(fileURLWithPath: "Model.sqlite", isDirectory: false, relativeTo: applicationSupportFolderURL)
#endif
    let options:Dictionary<AnyHashable, Any>? = [
        NSMigratePersistentStoresAutomaticallyOption : NSNumber(value: true),
        NSInferMappingModelAutomaticallyOption: NSNumber(value: true),
        NSPersistentHistoryTrackingKey: NSNumber(value: true)
    ]
    
    if let _ = try? moc.persistentStoreCoordinator!.addPersistentStore(type: NSPersistentStore.StoreType.sqlite, configuration: "Local", at: storeURL, options: options) {
        print("success")
    } else {
        print("failed")
    }
}

此处需要注意两点:

  1. macCatalyst下的sqlite位置与iOS不同,前面多了一个应用名。(行11到15)
  2. 如果你之前使用过CloudKit同步,那么还需要多一个“ NSPersistentHistoryTrackingKey: NSNumber(value: true)”。(行20)否则数据库就会以只读方式打开,无法同步。

3. 打开新数据库

这个就是融合升级之前打开的方式。值得一提的是,如果你融合升级之后,还想要对于数据库的内容进行额外的处理。那么应该在这一步完成之后,进行处理。