显然,NSFetchedResultsController
不知道给定NSManagedObjectContext
上的数据已通过执行NSBatchUpdateRequest
而发生更改。是否可以强制NSFetchedResultsController
重新加载其数据,以便调用controller(_:didChange:at:for:newIndexPath:)
上的NSFetchedResultsControllerDelegate
?
我正在使用访存结果控制器来通知底层数据的更改,因此可以在表视图更改时更新它。我正在采用NSFetchedResultsControllerDelegate
协议并实现controller(_:didChange:at:for:newIndexPath:)
方法。一切似乎都正常,因为在我插入,删除或更新单个项目时调用了did方法。
当我使用NSBatchUpdateRequest
批量更新多个项目时,会发生问题。当我这样做时,不会调用委托方法controller(_:didChange:at:for:newIndexPath:)
(我在获取的结果控制器使用的相同上下文中执行这些更新)。
#1:获取的结果控制器初始化:
func createFetchedResultsController() -> NSFetchedResultsController {
let fetchRequest: NSFetchRequest<EAlbum> = NSFetchRequest<EAlbum>(entityName: "EAlbum")
fetchRequest.predicate = NSCompoundPredicate(type: .and, subpredicates: createPredicates())
addSortDescriptors(on: fetchRequest)
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: AppDelegate.myPersistentContainer.viewContext,
sectionNameKeyPath: nil,
cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
}
#2:NSFetchedResultsControllerDelegate:
extension FeedTableViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChange anObject: Any,
at indexPath: IndexPath?,
for type: NSFetchedResultsChangeType,
newIndexPath: IndexPath?) {
switch type {
case .insert:
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
default:
// TODO
}
}
}
#3:使用NSBatchUpdateRequest
从AppDelegate.myPersistentContainer.viewContext
更新一堆项目的功能:
func markAllAsRead(using context: NSManagedObjectContext) -> Bool {
let request = NSBatchUpdateRequest(entityName: "EAlbum")
request.predicate = NSPredicate(format: "%K == YES", #keyPath(EAlbum.markUnread))
request.propertiesToUpdate = [#keyPath(EAlbum.markUnread): false]
request.resultType = .statusOnlyResultType
do {
let result = try context.execute(request) as? NSBatchUpdateResult
return result?.result as? Bool ?? false
} catch {
return false
}
}
#4:仅更新AppDelegate.myPersistentContainer.viewContext
中一项的功能:
func updateUnreadFlag(albumId: String, markUnread: Bool, using context: NSManagedObjectContext) {
guard let album = try? EAlbum.findAlbum(matching: albumId, in: context) else { return }
album.markUnread = markUnread
do {
try context.save()
} catch {
// Handle error
}
}
调用#4 时,将调用委托controller(_:didChange:at:for:newIndexPath:)
方法。但是在执行#3 时,不会调用委托方法。似乎NSFetchedResultsController
不知道通过执行NSManagedObjectContext
而发生的给定NSBatchUpdateRequest
上的数据已更改。
由于我的提取结果控制器不知道数据已更改,因此我考虑过像这样手动重新加载数据:
func reloadFetchedResultsController() {
fetchedResultsController = createFetchedResultsController()
do {
try fetchedResultsController.performFetch()
} catch {
// Handle error
}
tableView.reloadData()
}
因此,每次执行#3 之后,我都会调用reloadFetchedResultsController()
重新加载获取的结果控制器和表视图。但是由于某种原因,它似乎加载了旧数据,因为我的表视图没有任何变化。
A:是否可以让获取的结果控制器在执行批处理更新时知道数据已更改?
B:是否可以强制我的提取结果控制器重新加载其数据?因为,作为最后的选择,我可以在执行#3 之后强制在获取的结果控制器上重新加载,最后在此之后强制执行表视图更新,因此它将使用新的批处理重新加载其单元格,从提取的结果控制器更新数据。
答案 0 :(得分:1)
执行批处理更新请求后,您必须将更改合并到上下文中。
尝试
func markAllAsRead(using context: NSManagedObjectContext) -> Bool {
let request = NSBatchUpdateRequest(entityName: "EAlbum")
request.predicate = NSPredicate(format: "%K == YES", #keyPath(EAlbum.markUnread))
request.propertiesToUpdate = [#keyPath(EAlbum.markUnread): false]
request.resultType = .updatedObjectIDsResultType
do {
let result = try context.execute(request) as? NSBatchUpdateResult
guard let objectIDArray = result?.result as? [NSManagedObjectID] else { return false }
let changes = [NSUpdatedObjectsKey : objectIDArray]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [context])
return true
} catch {
return false
}
}