问题描述
我有一个 NSFetchedResultsController
,我正在尝试在后台上下文中更新我的数据。例如,这里我试图删除一个对象:
I have an NSFetchedResultsController
and I am trying to update my data on a background context. For example, here I am trying to delete an object:
persistentContainer.performBackgroundTask { context in
let object = context.object(with: restaurant.objectID)
context.delete(object)
try? context.save()
}
我有两件事我不明白:
- 我原本希望这可以修改,但不能保存父上下文。但是,肯定会保存父上下文(通过手动打开SQLite文件进行验证)。
- 我原本期望
NSFetchedResultsController
在后台内容保存回其父级时更新,但这不会发生。我是否需要在主线程上手动触发某些内容?
- I would have expected this to modify, but not save the parent context. However, the parent context is definitely being saved (as verified by manually opening the SQLite file).
- I would have expected the
NSFetchedResultsController
to update when the background content saves back up to its parent, but this is not happening. Do I need to manually trigger something on the main thread?
显然有一些我没有得到的东西。任何人都可以解释一下吗?
Obviously there is something I am not getting. Can anybody explain this?
我知道我已经正确实现了获取的结果控制器委托方法,因为如果我改变我的代码直接更新 viewContext
,一切都按预期工作。
I know that I have implemented the fetched results controller delegate methods correctly, because if I change my code to directly update the viewContext
, everything works as expected.
推荐答案
说明
NSPersistentContainer
的实例方法 performBackgroundTask(_:)
和 newBackgroundContext( )
记录不完整。
Explanation
NSPersistentContainer
's instance methods performBackgroundTask(_:)
and newBackgroundContext()
are poorly documented.
无论你调用哪种方法,在任何一种情况下(返回)临时 NSManagedObjectContext
使用 privateQueueConcurrencyType
设置,并直接与 NSPersistentStoreCoordinator
相关联,因此没有 parent
。
No matter which method you call, in either case the (returned) temporary NSManagedObjectContext
is set up with privateQueueConcurrencyType
and is associated with the NSPersistentStoreCoordinator
directly and therefore has no parent
.
参见:
...或者自己确认:
... or confirm it yourself:
persistentContainer.performBackgroundTask { (context) in
print(context.parent) // nil
print(context.persistentStoreCoordinator) // Optional(<NSPersistentStoreCoordinator: 0x...>)
}
let context = persistentContainer.newBackgroundContext()
print(context.parent) // nil
print(context.persistentStoreCoordinator) // Optional(<NSPersistentStoreCoordinator: 0x...>)
由于缺少父
,更改不会被提交到父上下文
例如 viewContext
并且 viewContext
未触动,连接的 NSFetchedResultsController
将无法识别任何更改,因此不会更新或调用其委托
的方法。相反,更改将直接推送到持久性存储协调器
,然后保存到持久性存储
。
Due to the lack of a parent
, changes won't get committed to a parent context
like e.g. the viewContext
and with the viewContext
untouched, a connected NSFetchedResultsController
won’t recognize any changes and therefore doesn’t update or call its delegate
's methods. Instead changes will be pushed directly to the persistent store coordinator
and after that saved to the persistent store
.
我希望,我能够帮助你,如果你需要进一步的帮助,我可以补充一下,如何按照你的描述获得所需的行为,回答。 (解决方案在下面添加)
I hope, that I was able to help you and if you need further assistance, I can add, how to get the desired behavior, as described by you, to my answer. (Solution added below)
您通过使用两个具有父子关系的 NSManagedObjectContext
来实现您所描述的行为:
You achieve the behavior, as described by you, by using two NSManagedObjectContext
s with a parent-child relationship:
// Create new context for asynchronous execution with privateQueueConcurrencyType
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
// Add your viewContext as parent, therefore changes are pushed to the viewContext, instead of the persistent store coordinator
let viewContext = persistentContainer.viewContext
backgroundContext.parent = viewContext
backgroundContext.perform {
// Do your work...
let object = backgroundContext.object(with: restaurant.objectID)
backgroundContext.delete(object)
// Propagate changes to the viewContext -> fetched results controller will be notified as a consequence
try? backgroundContext.save()
viewContext.performAndWait {
// Save viewContext on the main queue in order to store changes persistently
try? viewContext.save()
}
}
但是,你也可以坚持使用 performBackgroundTask(_:)
或使用 newBackgroundContext()
。但如前所述,在这种情况下,更改将直接保存到持久性存储中,并且默认情况下不会更新 viewContext
。为了将 down 的更改传播到 viewContext
,这会导致 NSFetchedResultsController
被通知,你必须将 viewContext.automaticallyMergesChangesFromParent
设置为 true
:
However, you can also stick with performBackgroundTask(_:)
or use newBackgroundContext()
. But as said before, in this case changes are saved to the persistent store directly and the viewContext
isn't updated by default. In order to propagate changes down to the viewContext
, which causes NSFetchedResultsController
to be notified, you have to set viewContext.automaticallyMergesChangesFromParent
to true
:
// Set automaticallyMergesChangesFromParent to true
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
persistentContainer.performBackgroundTask { context in
// Do your work...
let object = context.object(with: restaurant.objectID)
context.delete(object)
// Save changes to persistent store, update viewContext and notify fetched results controller
try? context.save()
}
请注意进行大量更改,例如添加10.000个对象曾经可能会驱动你的 NSFetchedResultsController
疯狂,因此阻止主队列
。
Please note that extensive changes such as adding 10.000 objects at once will likely drive your NSFetchedResultsController
mad and therefore block the main queue
.
这篇关于使用performBackgroundTask更新NSFetchedResultsController的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!