代码之家  ›  专栏  ›  技术社区  ›  Reinhard Männer

如何设置两个使用相同内存持久存储的核心数据堆栈?

  •  0
  • Reinhard Männer  · 技术社区  · 2 年前

    设置:

    我的应用程序使用核心数据&云工具包镜像。
    对于单元测试,我想通过设置来模拟iCloud镜像 cloudKitContainerOptions = nil NSPersistentStoreDescription 使用的持久存储的。
    为了模拟镜像,我想设置一个第二核心数据堆栈,它使用与普通数据堆栈相同的持久存储。
    此外,SQL持久存储被 NSInMemoryStoreType 持久存储。

    问题:

    我没能为2个核心数据堆栈使用相同的内存持久存储。
    两个堆栈都使用 NSPersistentCloudKitContainer
    两者使用相同 NSPersistentStoreDescription 使用相同的文件URL,尽管对于内存中的持久存储,该文件URL显然被忽略了。
    因此,两个容器使用不同的内存中持久存储,并且不可能模拟iCloud镜像到单个持久存储。

    问题:

    我的预期设置是可能的,如果是,如何设置?

    PS: 我知道,通过指定相同的文件UrL,我可能可以使用相同的SQL存储。但这有一个缺点,即存储在不同的单元测试之间持续存在,并且必须在每次测试开始时重置。

    0 回复  |  直到 2 年前
        1
  •  0
  •   Reinhard Männer    2 年前

    确实可以设置两个使用相同内存持久存储的核心数据堆栈,但它们并不是所有属性都作为SQLite存储。
    以下是我对2个核心数据堆栈的测试设置:

            let coreDataCloudKitContainer = CoreDataCloudKitContainer(name: appName, privateStoreType: .persistentStore)
    //      let coreDataCloudKitContainer = CoreDataCloudKitContainer(name: appName, privateStoreType: .nullDevice)
    //      let coreDataCloudKitContainer = CoreDataCloudKitContainer(name: appName, privateStoreType: .inMemory)
            coreDataManager.persistentContainer = coreDataCloudKitContainer
            let privateStore = coreDataCloudKitContainer.privateStore!
            let mockStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: CoreDataCloudKitContainer.managedObjectModel)
            _ = try! mockStoreCoordinator.addPersistentStore(type: NSPersistentStore.StoreType(rawValue: privateStore.type), 
                                                             configuration: privateStore.configurationName, 
                                                             at: privateStore.url!,
                                                             options: [NSPersistentHistoryTrackingKey: true])
            
            let viewContext = coreDataCloudKitContainer.viewContext
            let mockViewContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
            mockViewContext.persistentStoreCoordinator = mockStoreCoordinator
    

    CoreDataCloudKitContainer 是的子类 NSPersistentCloudKitContainer 。它有一个静态 var managedObjectModel init CoreDataCloudKitContainer 以及 初始化 第2页 NSPersistentStoreCoordinator (模型只能加载一次,否则核心数据会混淆):

    static var managedObjectModel: NSManagedObjectModel = {
        guard let modelFile = Bundle.main.url(forResource: appName, withExtension: "momd") else { fatalError("Canot find model file") }
        guard let model = NSManagedObjectModel(contentsOf: modelFile) else { fatalError("Cannot parse model file") }
        return model
    }()  
    

    还要注意的是 addPersistentStore 使用选项 [NSPersistentHistoryTrackingKey: true] 如果 privateStore 定义为 privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey) 。如果未设置,则会出现错误 CoreData: fault: Store opened without NSPersistentHistoryTrackingKey but previously had been opened with NSPersistentHistoryTrackingKey - Forcing into Read Only mode store at 'file://...

    privateStore 初始化为

    let privateStoreURL: URL
    switch privateStoreType {
        case .persistentStore:
            privateStoreURL = CoreDataCloudKitContainer.appDefaultDirectoryURL.appendingPathComponent("Private.sqlite")
        case .nullDevice, .inMemory:
            privateStoreURL = URL(fileURLWithPath: "/dev/null")
    }
    print("privateStoreURL: \(privateStoreURL)")
    let privateStoreDescription = NSPersistentStoreDescription(url: privateStoreURL)
    privateStoreDescription.url = privateStoreURL
    privateStoreDescription.configuration = privateConfigurationName
    privateStoreDescription.timeout = timeout
    privateStoreDescription.type = type
    privateStoreDescription.isReadOnly = isReadOnly
    privateStoreDescription.shouldAddStoreAsynchronously = shouldAddStoreAsynchronously
    privateStoreDescription.shouldInferMappingModelAutomatically = shouldInferMappingModelAutomatically
    privateStoreDescription.shouldMigrateStoreAutomatically = shouldMigrateStoreAutomatically
    // The options below have to be set before loadPersistentStores
    // Enable history tracking and remote notifications
    privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
    privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
    if isTesting {
        privateStoreDescription.cloudKitContainerOptions = nil
    } else {
        privateStoreDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: kICloudContainerID)
        privateStoreDescription.cloudKitContainerOptions!.databaseScope = .private
    }  
    

    其中类型为 NSSQLiteStoreType 对于 privateStoreType == .persistentStore .nullDevice NSInMemoryStoreType 对于 privateStoreType == .inMemory 。请注意,选项 .nullDevice 仅在内存中创建SQLite存储,但是 .inMemory 创建 一些 存储在内存中,但它是 SQLite存储。关于 .nullDevice 可以在中找到商店 this blog

    警告:
    尽管可以使用相同的内存中持久存储设置2个核心数据堆栈,但这种存储有局限性。一个是通过一个堆栈修改存储不会触发 NSPersistentStoreRemoteChangeNotification ,因此无法对iCloud镜像进行单元测试。为此,必须使用基于文件的SQLite持久存储。