初始化时内存中的 CoreData 容器为空

In memory CoreData container is empty when initialized

tl;博士:

  1. 克隆项目:https://github.com/Jasperav/CoreDataInMemoryFail
  2. 运行 测试失败。为什么我的内存容器没有任何数据,我如何确保它有数据?

长:

我有一个包含填充数据的 sqlite 文件,我在 CoreData 中有一个内存数据库。一些代码:

// ...
func createInMemoryPerformanceTestDatabase() -> NSPersistentContainer {
    let url = createPathToSomeSQLiteFile()
    let container = NSPersistentContainer(name: dataModelName, managedObjectModel: objectModel)
    let description = NSPersistentStoreDescription(url: url)

    description.type = NSInMemoryStoreType

    container.persistentStoreDescriptions = [description]

    container.loadPersistentStores { description, error in
        XCTAssertNil(error)
    }

    return container
}
// ...

虽然 sqlite 文件中有数据,但我在使用 container 创建的上下文中看不到它。

当我使用指向包含数据的 sqlite 文件的 CoreData 创建内存数据库时,查询数据库时没有看到任何结果。我想查看 sqlite 文件中的数据。数据应该全部加载到内存中。这是为了测试目的。

InMemoryType 没有像其他答案建议的那样从您的 url 加载日期。如果您需要从文件加载数据,那么请使用提到的迁移方法,但是如果您只需要用随机数据填充它以进行测试,那么这里是另一种解决方案。

import CoreData
import XCTest
@testable import CoreDataInMemoryFail


class CoreDataInMemoryFailTests: XCTestCase {

    var persistentContainer: NSPersistentContainer!
    var inMemoryContainer: NSPersistentContainer!

    override func setUp() {
        super.setUp()
        persistentContainer = createContainer(modify: { _ in })
        inMemoryContainer = createContainer { storeDescription in
            storeDescription.type = NSInMemoryStoreType
        }
        initStubs()
    }

    override class func tearDown() {
        super.tearDown()
    }

    private func createContainer(modify: (NSPersistentStoreDescription) -> ()) -> NSPersistentContainer {
        let bundle = Bundle(for: type(of: self))
        let path = bundle.path(forResource: "InMemoryDatabase", ofType: "sqlite")!
        let url = URL(fileURLWithPath: path)
        let fileManager = FileManager.default
        let uuid = UUID().uuidString

        let saveDirectory = fileManager
                .urls(for: .cachesDirectory, in: .userDomainMask)[0]
                .appendingPathComponent(uuid)

        let saveLocation = saveDirectory.appendingPathComponent(url.lastPathComponent)

        try! fileManager.createDirectory(at: saveDirectory, withIntermediateDirectories: false)
        try! fileManager.copyItem(at: url, to: saveLocation)

        let persistentContainer = createPersistentContainer(dataModelName: "InMemoryDatabase")
        let storeDescription = NSPersistentStoreDescription(url: saveLocation)


        modify(storeDescription)
        print("TYPE OF STORE IS: \(storeDescription)")
        persistentContainer.persistentStoreDescriptions = [storeDescription]
        persistentContainer.loadPersistentStores { description, error in
            XCTAssertEqual(storeDescription.type, description.type)
            XCTAssertNil(error)
        }

        return persistentContainer
    }

    func initStubs() {

        func inserPerson( age: Int32) -> Person? {
            let obj = NSEntityDescription.insertNewObject(forEntityName: "Person", into: inMemoryContainer.viewContext)

            obj.setValue(age, forKey: "age")

            return obj as? Person
        }

        _ = inserPerson(age: 1)
        _ = inserPerson(age: 2)
        _ = inserPerson(age: 3)
        _ = inserPerson(age: 4)
        _ = inserPerson(age: 5)

        do {
            try inMemoryContainer.viewContext.save()
        }  catch {
            print("create fakes error \(error)")
        }

    }

    func removeData() {
        let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
        let objs = try! inMemoryContainer.viewContext.fetch(fetchRequest)
        for case let obj as NSManagedObject in objs {
            inMemoryContainer.viewContext.delete(obj)
        }
        try! inMemoryContainer.viewContext.save()
    }

    func testFail() {
        let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
        let persistentContainerCount = (try! persistentContainer.viewContext.fetch(fetchRequest)).count
        let inMemoryContainerCount = (try! inMemoryContainer.viewContext.fetch(fetchRequest)).count

        XCTAssertEqual(8, persistentContainerCount)
        XCTAssertEqual(5, inMemoryContainerCount)
    }

}

More info can be found here

您所尝试的问题是在将它们加载到容器之前将 storeDescription 的类型设置为 NSInMemoryStoreType。由于 storeDescription 的类型被声明为 NSInMemoryStoreType api 将不会从您提供的文件 URL 中读取和填充数据。为了让 api 从文件 url 中读取数据,storeDescription 的类型必须是使用初始化程序 init(url: URL) 初始化定义的类型,即 SQLite 你的情况。

但是,如果您想要一个 NSInMemoryStoreType 类型的 persistentStore 以及从文件 url 读取的数据,您可以使用函数 migratePersistentStore:toURL:options:withType:error: 迁移 NSInMemoryStoreType 类型的 persistentContainer 的 persistentStore。您可以尝试下面的代码片段。

import CoreData
import XCTest
@testable import CoreDataInMemoryFail


class CoreDataInMemoryFailTests: XCTestCase {

    private func createContainer(modify: (NSPersistentContainer) -> ()) -> NSPersistentContainer {
        let bundle = Bundle(for: type(of: self))
        let path = bundle.path(forResource: "InMemoryDatabase", ofType: "sqlite")!
        let url = URL(fileURLWithPath: path)
        let persistentContainer = createPersistentContainer(dataModelName: "InMemoryDatabase")
        let storeDescription = NSPersistentStoreDescription(url: url)

        persistentContainer.persistentStoreDescriptions = [storeDescription]
        persistentContainer.loadPersistentStores { description, error in
            XCTAssertEqual(storeDescription.type, description.type)
            XCTAssertNil(error)
        }

        modify(persistentContainer)
        return persistentContainer
    }

    func testFail() {
        let persistentContainer = createContainer(modify: { _ in })
        let inMemoryContainer = createContainer { persistentContainer in
            let coordinator = persistentContainer.persistentStoreCoordinator
            coordinator.persistentStores.forEach { (persistentStore) in
                do {
                    try coordinator.migratePersistentStore(persistentStore, to: NSPersistentContainer.defaultDirectoryURL(), options: nil, withType: NSInMemoryStoreType)
                } catch {
                    print("Error while migrating persistentStore")
                }
            }
        }

        let persistentContainerCoordinator = persistentContainer.persistentStoreCoordinator
        persistentContainerCoordinator.persistentStores.forEach { (persistentStore) in
            XCTAssertEqual(persistentStore.type, "SQLite")
        }

        let inMemoryContainerCoordinator = inMemoryContainer.persistentStoreCoordinator
        inMemoryContainerCoordinator.persistentStores.forEach { (persistentStore) in
            XCTAssertEqual(persistentStore.type, NSInMemoryStoreType)
        }

        let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
        let persistentContainerCount = (try! persistentContainer.viewContext.fetch(fetchRequest)).count
        let inMemoryContainerCount = (try! inMemoryContainer.viewContext.fetch(fetchRequest)).count

        XCTAssertEqual(8, persistentContainerCount)
        XCTAssertEqual(persistentContainerCount, inMemoryContainerCount)
    }

}

在上面的代码片段中,我还添加了断言来验证您的 inMemoryContainer 和 [=19= 中的 persistentStore 类型是否为 NSInMemoryStoreType ]SQLite 在你的 persistentContainer 中。希望对你有帮助。