NSFetchedResultsController 不知道数据已被 NSBatchUpdateRequest 更改
Posted
技术标签:
【中文标题】NSFetchedResultsController 不知道数据已被 NSBatchUpdateRequest 更改【英文标题】:NSFetchedResultsController doesn't know data was changed with a NSBatchUpdateRequest 【发布时间】:2019-12-21 20:56:39 【问题描述】:TL;DR
显然,当通过执行 NSBatchUpdateRequest
发生更改时,NSFetchedResultsController
不知道给定 NSManagedObjectContext
上的数据已更改。是否可以强制NSFetchedResultsController
重新加载其数据,以便调用NSFetchedResultsControllerDelegate
上的controller(_:didChange:at:for:newIndexPath:)
?
问题
我正在使用获取的结果控制器来获知基础数据的变化,以便在表格视图发生变化时更新它。我正在采用NSFetchedResultsControllerDelegate
协议并实现controller(_:didChange:at:for:newIndexPath:)
方法。一切似乎都在工作,因为当我插入、删除或更新单个项目时调用了 did change 方法。
当我使用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 时,委托方法不会被调用。当通过执行 NSBatchUpdateRequest
发生更改时,NSFetchedResultsController
似乎不知道给定 NSManagedObjectContext
上的数据已更改。
我尝试过的解决方法
由于我的获取结果控制器不知道数据已更改,因此我考虑像这样手动重新加载它:
func reloadFetchedResultsController()
fetchedResultsController = createFetchedResultsController()
do
try fetchedResultsController.performFetch()
catch
// Handle error
tableView.reloadData()
所以每次执行 #3 后,我都会调用reloadFetchedResultsController()
重新加载获取的结果控制器和表格视图。但由于某种原因,它似乎加载了旧数据,因为我的表格视图没有任何变化。
问题
答:是否可以让获取结果控制器知道在执行批量更新时数据已更改?
B:是否可以强制我的获取结果控制器重新加载其数据?因为,作为最后的手段,我可以在执行 #3 后强制重新加载获取的结果控制器,最后强制更新表格视图,所以它会使用新的批处理重新加载它的单元格-从获取的结果控制器更新数据。
【问题讨论】:
【参考方案1】:来自documentation:
执行批量更新请求后,您必须将更改合并到上下文中。
试试
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
【讨论】:
以上是关于NSFetchedResultsController 不知道数据已被 NSBatchUpdateRequest 更改的主要内容,如果未能解决你的问题,请参考以下文章