core-data - 多个 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)
}
}
我在做什么导致我的测试出现错误?
解决方案
后自动缓存
这不应该再发生了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
调用时重用该模型,它有一个构造函数来使用给定的模型,而不是从磁盘再次加载它。
推荐阅读
- php - 如何使用 PHPUnit 忽略特定的私有静态函数?
- kafka-consumer-api - Kafka Avro 消息反序列化问题
- javascript - 如何在反应裁剪组件上显示图标?
- c# - 如何使用 selenium 和 C# webdriver 创建和保存 Firefox 配置文件?
- web - 开发类似于浏览器堆栈的 Web 应用程序
- mmu - 如何理解图中的ARMv8 AArch64 MMU表描述符格式?
- python - Django:将模型对象传递给 context_processors
- java - 如何减少 Hibernate Spring 中的 DB 命中数
- java - 有没有更优化的方式来遍历这个列表?
- php - Artisan::call('config:cache') 无法从控制器工作。从控制器运行它的可能方法是什么