本文介绍了多个 NSEntityDescriptions 声明 NSManagedObject 子类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在创建一个允许我使用 Core Data 的框架.在框架的测试目标中,我配置了一个名为MockModel.xcdatamodeld 的数据模型.它包含一个名为 MockManaged 的实体,该实体具有一个 Date 属性.

I am creating a framework that allows me to use Core Data. In the framework's test target, I have configured a data model named MockModel.xcdatamodeld. It contains a single entity named MockManaged that has a single Date property.

为了测试我的逻辑,我正在创建一个内存存储.当我想验证我的保存逻辑时,我创建了一个内存存储实例并使用它.但是,我一直在控制台中收到以下输出:

So that I can test my logic, I am creating an in-memory store. When I want to validate my saving logic, I create an instance of the in-memory store and use it. However, I keep getting the following output in the console:

2018-08-14 20:35:45.340157-0400 xctest[7529:822360] [error] warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'LocalPersistenceTests.MockManaged' so +entity is unable to disambiguate.
CoreData: warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'LocalPersistenceTests.MockManaged' so +entity is unable to disambiguate.
2018-08-14 20:35:45.340558-0400 xctest[7529:822360] [error] warning:     'MockManaged' (0x7f986861cae0) from NSManagedObjectModel (0x7f9868604090) claims 'LocalPersistenceTests.MockManaged'.
CoreData: warning:       'MockManaged' (0x7f986861cae0) from NSManagedObjectModel (0x7f9868604090) claims 'LocalPersistenceTests.MockManaged'.
2018-08-14 20:35:45.340667-0400 xctest[7529:822360] [error] warning:     'MockManaged' (0x7f986acc4d10) from NSManagedObjectModel (0x7f9868418ee0) claims 'LocalPersistenceTests.MockManaged'.
CoreData: warning:       'MockManaged' (0x7f986acc4d10) from NSManagedObjectModel (0x7f9868418ee0) claims 'LocalPersistenceTests.MockManaged'.
2018-08-14 20:35:45.342938-0400 xctest[7529:822360] [error] error: +[LocalPersistenceTests.MockManaged entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
CoreData: error: +[LocalPersistenceTests.MockManaged entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass

以下是我用来创建内存存储的对象:

Below is the object I use to create my in-memory stores:

class MockNSManagedObjectContextCreator {

    // MARK: - NSManagedObjectContext Creation

    static func inMemoryContext() -> NSManagedObjectContext {
        guard let model = NSManagedObjectModel.mergedModel(from: [Bundle(for: self)]) else { fatalError("Could not create model") }
        let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
        do {
            try coordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)
        } catch {
            fatalError("Could not create in-memory store")
        }
        let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
        context.persistentStoreCoordinator = coordinator
        return context
    }

}

以下是我的 MockManaged 实体的组成:

Below is what makes up my MockManaged entity:

class MockManaged: NSManagedObject, Managed {

    // MARK: - Properties

    @NSManaged var date: Date

}

以下是我的XCTestCase:

class Tests_NSManagedObjectContext: XCTestCase {

    // MARK: - Object Insertion

    func test_NSManagedObjectContext_InsertsManagedObject_WhenObjectConformsToManagedProtocol() {
        let context = MockNSManagedObjectContextCreator.inMemoryContext()
        let changeExpectation = expectation(forNotification: .NSManagedObjectContextObjectsDidChange, object: context, handler: nil)
        let object: MockManaged = context.insertObject()
        object.date = Date()
        wait(for: [changeExpectation], timeout: 2)
    }

    // MARK: - Saving

    func test_NSManagedObjectContext_Saves_WhenChangesHaveBeenMade() {
        let context = MockNSManagedObjectContextCreator.inMemoryContext()
        let saveExpectation = expectation(forNotification: .NSManagedObjectContextDidSave, object: context, handler: nil)
        let object: MockManaged = context.insertObject()
        object.date = Date()
        do {
            try context.saveIfHasChanges()
        } catch {
            XCTFail("Expected successful save")
        }
        wait(for: [saveExpectation], timeout: 2)
    }

    func test_NSManagedObjectContext_DoesNotSave_WhenNoChangesHaveBeenMade() {
        let context = MockNSManagedObjectContextCreator.inMemoryContext()
        let saveExpectation = expectation(forNotification: .NSManagedObjectContextDidSave, object: context, handler: nil)
        saveExpectation.isInverted = true
        do {
            try context.saveIfHasChanges()
        } catch {
            XCTFail("Unexpected error: (error)")
        }
        wait(for: [saveExpectation], timeout: 2)
    }

}

我在做什么导致我的测试出现错误?

What am I doing that is causing the errors in my tests?

推荐答案

后自动缓存

NSPersistent[CloudKit]Container(name: String) 不应再发生这种情况,因为它现在似乎会自动缓存模型(Swift 5.1、Xcode11、iOS13/MacOS10.15).

Post-automatic-caching

This should not happen anymore with NSPersistent[CloudKit]Container(name: String), since it seems to cache the model automatically now (Swift 5.1, Xcode11, iOS13/MacOS10.15).

NSPersistentContainer/NSPersistentCloudKitContainer 确实有两个构造函数:

NSPersistentContainer/NSPersistentCloudKitContainer does have two constructors:

第一个只是一个方便的初始化器,使用从磁盘加载的模型调用第二个.问题是在同一个 app/test invocation 中从磁盘加载相同的 NSManagedObjectModel 两次会导致上述错误,因为模型的每次加载都会导致外部注册调用,在同一个 app/test invocation 上第二次调用时会打印错误.而且 init(name: String) 不够聪明,无法缓存模型.

The first is just a convenience initializer calling the second with a model loaded from disk. The trouble is that loading the same NSManagedObjectModel twice from disk inside the same app/test invocation results in the errors above, since every loading of the model results in external registration calls, which print errors once called a second time on the same app/test invocation.And init(name: String) was not smart enough to cache the model.

因此,如果您想多次加载容器,则必须加载一次 NSManagedObjectModel 并将其存储在一个属性中,然后在每个 init(name:managedObjectModel:)调用.

So if you want to load a container multiple time you have to load the NSManagedObjectModel once and store it in an attribute you then use on every init(name:managedObjectModel:) call.

import Foundation
import SwiftUI
import CoreData
import CloudKit

class PersistentContainer {
    private static var _model: NSManagedObjectModel?
    private static func model(name: String) throws -> NSManagedObjectModel {
        if _model == nil {
            _model = try loadModel(name: name, bundle: Bundle.main)
        }
        return _model!
    }
    private static func loadModel(name: String, bundle: Bundle) throws -> NSManagedObjectModel {
        guard let modelURL = bundle.url(forResource: name, withExtension: "momd") else {
            throw CoreDataError.modelURLNotFound(forResourceName: name)
        }

        guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
            throw CoreDataError.modelLoadingFailed(forURL: modelURL)
       }
        return model
    }

    enum CoreDataError: Error {
        case modelURLNotFound(forResourceName: String)
        case modelLoadingFailed(forURL: URL)
    }

    public static func container() throws -> NSPersistentCloudKitContainer {
        let name = "ItmeStore"
        return NSPersistentCloudKitContainer(name: name, managedObjectModel: try model(name: name))
    }
}

旧答案

加载核心数据有点神奇,从磁盘加载模型并使用它意味着它注册了某些类型.第二次加载尝试再次注册该类型,这显然告诉您已经为该类型注册了一些东西.

Old answer

Loading Core Data is a little bit of magic, where loading a model from disk and using it means it registers for certain types. A second loading tries to register for the type again, which obviously tells you that something registered for the type already.

您只能加载一次 Core Data 并在每次测试后清理该实例.清理意味着删除每个对象实体,然后保存.有一些功能可以为您提供所有实体,然后您可以获取和删除这些实体.批量删除在 InMemory 中是不可用的,所以它是逐个管理的对象.

You can load Core Data only once and cleanup that instance after each test. Cleanup means deleting every object entity and then saving. There is some function which gives you all entities which you can then fetch and delete. Batch delete is not available InMemory though so object-by-managed object it is there.

(可能更简单的)替代方法是加载一次模型,将其存储在某处并在每个 NSPersistentContainer 调用中重用该模型,它有一个构造函数来使用给定的模型,而不是从磁盘.

The (probably simpler) alternative is to load the model once, store it somewhere and reuse that model on every NSPersistentContainer call, it has a constructor to use a given model instead of loading it again from disk.

这篇关于多个 NSEntityDescriptions 声明 NSManagedObject 子类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-14 04:20