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:我使用NSBatchUpdateRequestAppDelegate.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 更改的主要内容,如果未能解决你的问题,请参考以下文章

在 Core Data 应用程序中调用 performFetch 后,是不是需要手动更新表视图?