首页 > 解决方案 > 多个 NSEntityDescriptions 声明 NSManagedObject 子类

问题描述

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

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

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

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

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实体的内容:

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)
    }

}

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

标签: core-datansmanagedobjectnsmanagedobjectcontext

解决方案


后自动缓存

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

预自动缓存

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

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

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

示例:缓存模型

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))
    }
}

旧答案

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

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

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


推荐阅读