我有一个 NSFetchedResultsController
,我正在尝试在后台上下文中更新我的数据。例如,这里我试图删除一个对象:
persistentContainer.performBackgroundTask { context in
let object = context.object(with: restaurant.objectID)
context.delete(object)
try? context.save()
}
有两件事我不明白:
- 我原以为这会修改父上下文,但不会保存。但是,肯定会保存父上下文(通过手动打开 SQLite 文件验证)。
- 我原以为
NSFetchedResultsController
会在后台内容保存回其父级时更新,但这并没有发生。我需要在主线程上手动触发某些东西吗?
显然有些东西我没有得到。谁能解释一下?
我知道我已经正确实现了获取结果 Controller 委托(delegate)方法,因为如果我更改我的代码以直接更新 viewContext
,一切都会按预期进行。
最佳答案
解释
NSPersistentContainer
的实例方法 performBackgroundTask(_:)
和 newBackgroundContext()
的文档很少。
无论您调用哪种方法,在任何一种情况下,(返回的)临时 NSManagedObjectContext
都是使用 privateQueueConcurrencyType
设置的,并且与 NSPersistentStoreCoordinator
相关联> 直接,因此没有 parent
。
参见 documentation :
Invoking this method causes the persistent container to create and return a new NSManagedObjectContext with the concurrencyType set to privateQueueConcurrencyType. This new context will be associated with the NSPersistentStoreCoordinator directly and is set to consume NSManagedObjectContextDidSave broadcasts automatically.
...或者自己确认一下:
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...>)
由于缺少parent
,更改不会提交到parent context
,例如viewContext
并且 viewContext
未被触及,连接的 NSFetchedResultsController
将无法识别任何更改,因此不会更新或调用其 委托(delegate)
的方法。相反,更改将直接推送到 persistent store coordinator
,然后保存到 persistent store
。
希望我能够为您提供帮助,如果您需要进一步的帮助,我可以在我的回答中添加如何获得您所描述的所需行为。 (下面添加了解决方案)
解决方案
您通过使用两个具有父子关系的 NSManagedObjectContext
来实现您所描述的行为:
// 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
。为了将更改向下传播到viewContext
,这会导致通知NSFetchedResultsController
,您必须设置viewContext.automaticallyMergesChangesFromParent
到 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
发疯,从而阻塞 main queue
。
关于ios - 使用 performBackgroundTask 更新 NSFetchedResultsController,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40892147/