多个NSEntityDescriptions声明NSManagedObject子类 [英] Multiple NSEntityDescriptions Claim NSManagedObject Subclass
问题描述
我正在创建一个允许使用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
$的唯一匹配b $ b
下面是我用来创建内存存储的对象:
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
实体:
class MockManaged: NSManagedObject, Managed {
// MARK: - Properties
@NSManaged var date: Date
}
以下是构成我的 XCTestCase
的原因:
Below is what makes up my 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)
}
}
我是什么ng会导致我的测试出错?
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:
- init(name: String)
- init(name: String, managedObjectModel model: NSManagedObjectModel)
第一个只是一个方便的初始化方法,调用第二个方法从磁盘加载的模型。问题在于,从同一 app / test调用
内的磁盘加载相同的 NSManagedObjectModel
两次会导致上述错误,因为每次加载模型都会导致外部注册调用,而该调用在同一次 app / test调用
上再次调用时会打印错误。
和 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(名称: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子类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!