NSFetchedResultsController 不知道数据已被 NSBatchUpdateRequest 更改

NSFetchedResultsController doesn't know data was changed with a NSBatchUpdateRequest

TL;DR

显然,NSFetchedResultsController 不知道给定 NSManagedObjectContext 上的数据发生了更改,而该更改是通过执行 NSBatchUpdateRequest 发生的。是否可以强制 NSFetchedResultsController 重新加载其数据,以便调用 NSFetchedResultsControllerDelegate 上的 controller(_:didChange:at:for:newIndexPath:)

问题

我正在使用获取的结果控制器来了解基础数据的变化,这样我就可以在 table 视图发生变化时更新它。我正在采用 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 时,委托方法不会被调用。似乎 NSFetchedResultsController 不知道给定的 NSManagedObjectContext 上的数据发生了变化,因为执行 NSBatchUpdateRequest.

发生了这种变化

我试过的解决方法

因为我的 fetched results controller 不知道数据被改变了,我想像这样手动重新加载它:

func reloadFetchedResultsController() {
    fetchedResultsController = createFetchedResultsController()

    do {
        try fetchedResultsController.performFetch()
    } catch {
        // Handle error
    }

    tableView.reloadData()
}

所以每次执行 #3 后,我都会调用 reloadFetchedResultsController() 重新加载获取的结果控制器和 table 视图。但出于某种原因,它似乎加载了旧数据,因为我的 table 视图没有任何变化。

问题

A:是否可以在执行批量更新时让抓取结果控制器知道数据已更改?

B: 是否可以强制我的 fetched results controller 重新加载它的数据?因为,作为最后的手段,我可​​以在执行 #3 后强制重新加载我的获取结果控制器,最后强制执行 table 视图更新,所以它会重新加载它是使用来自获取结果控制器的新批量更新数据的单元格。

来自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
    }
}