我有一个使用CoreData的应用程序,它具有多个背景上下文(NSManagedObjectContext)。
在编写一些测试时,我观察到一种奇怪的行为,似乎与官方文档存在争议: 来自一个上下文的更改会自动传播到另一上下文,而.automaticallyMergesChangesFromParent在这两个上下文中均设置为false。
两个上下文都是从NSPersistentContainer接收的-一个是从.viewContext接收的,另一个是-.newBackgroundContext()函数的。
如save所述,在上下文保存中,更改将提交到上下文的父存储,即NSPersistentContainer。
但是实际上,尽管自动将MergesChangesFromParent == false(这是默认值),更改也出现在另一个上下文中。
let persistentContainer = NSPersistentContainer(name: "TESTING")
let mainContext = persistentContainer.viewContext
let otherContext = persistentContainer.newBackgroundContext()
//test entity is created on anotherContext
let entity: TestEntity = NSEntityDescription.insertNewObject
(forEntityName: String(describing: TestEntity.self),
into: anotherContext) as! TestEntity
entity.statusCode = "Testing"
//prepare fetch request for test entity
let fetchReq: NSFetchRequest<TestEntity> = TestEntity.fetchRequest()
fetchReq.predicate = NSPredicate(format: "statusCode = %@",
argumentArray: ["Testing"])
//ensure that entity is not present in mainContext
let entityFromMain = try! mainContext.fetch(fetchReq)
XCTAssertEqual(entityFromMain.count, 0)
//save context that has entity
try! otherContext.save()
//ensure that changes from parent store aren't merged automatically
XCTAssertFalse(mainContext.automaticallyMergesChangesFromParent)
//get inserted entity from mainContext
let entityOnMainAfterSaving = try! mainContext.fetch(fetchReq)
//entity is present in mainContext
XCTAssertTrue(entityOnMainAfterSaving.count > 0)
预期的输出-尽管没有刷新mainContext,entityOnMainAfterSaving不应包含新创建的实体,但它已经存在。
更新:
我问这个问题是因为在我的应用中,存在以下情况:
1.在otherContext中更改实体的属性
2. otherContext已保存
3.通过viewContext接收实体
4.属性值未更新为第1页(!)
同时,如果在抓取p.3之前立即调用viewContext.refreshAllObjects(),则属性值将得到更新
答案 0 :(得分:0)
这是预期的输出。
您要在后台上下文中保存实体。背景上下文是来自持久性容器的上下文。方法save告诉我们以下信息:
尝试将未保存的对注册对象的更改提交给 上下文的父店。
backgroundContext
的父存储是什么? persistentContainer
。viewContext
的父存储是什么? persistentContainer
。在viewContext上进行查询时,您将收到数据库中的所有值。当您在backgroundContext
上调用save时,它将告诉上下文的父存储区(persistentContainer
)要保存。保存后,这些值将在“真实数据库”中提交,而不仅在私有背景上下文中提交。
您可以说viewContext
的特殊之处在于它查询最新的提交数据。如果您有2个私有背景上下文并将automaticallyMergesChangesFromParent
设置为false
,则它们不会合并回彼此的提交操作,因此,在私有背景上下文A中保存的查询对象将在私有背景下不可见上下文B。
如果将automaticallyMergesChangesFromParent
设置为true
,则将根据其他上下文的提交(如果它们源自同一持久性容器)来更新后台上下文
在viewContext
中不合并来自其他上下文的提交绝对是愚蠢的,我想您可以弄清楚为什么它是不可维护的。
答案 1 :(得分:0)
您似乎在混淆automaticallyMergesChangesFromParent
或将上下文与获取合并在一起。
fetch
将始终与持久存储区保持联系,而不考虑上下文是否已与另一个合并。这就是获取在核心数据中的工作方式。本书(https://www.objc.io/books/core-data/)上有一节“避免获取请求”解释了同一件事。我的解释是:
“最大的性能违规者是获取请求。提取请求 必须遍历整个核心数据堆栈。根据API合约, 请求(即使它起源于托管对象上下文) 将查询文件系统中的SQLite存储。
因此,获取请求本来就很昂贵。”
节选自:Florian Kugler。 “核心数据”。
第25页的另一个内容,获取请求:
“我们现在要指出的一件事是:每次您 执行获取请求,核心数据将遍历完整的核心数据 堆栈,一直到文件系统。根据合同,提取请求是 往返:从上下文到持久存储 协调器和持久性存储,直到SQLite,然后是所有 回来。”
节选自:Florian Kugler。 “核心数据”。
这就是为什么即使您关闭了automaticallyMergesChangesFromParent
,提取操作仍会从数据库中读取最新的值。
2015年和2016年WWDC关于核心数据的会议很好(嗯,它们都是),我建议您仔细阅读。在过去的五年左右的时间里,几乎没有六次会议。通过观看这些会议的最佳实践以及这些年来添加到Core Data中的增量更改,我学到了很多东西。
如果您出于任何原因希望继续查看数据的旧快照(也许您的背景上下文正在不断添加/删除条目,并且您还没有准备好刷新视图上下文),那么建议您看一下查询生成。它应该给您您想要达到的目标。