NSFetchedResultsController不知道数据已通过NSBatchUpdateRequest

时间:2019-12-21 20:56:39

标签: ios core-data nsfetchedresultscontroller nsbatchupdaterequest

TL; DR

显然,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:使用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 时,不会调用委托方法。似乎NSFetchedResultsController不知道通过执行NSManagedObjectContext而发生的给定NSBatchUpdateRequest上的数据已更改。

我尝试过的解决方法

由于我的提取结果控制器不知道数据已更改,因此我考虑过像这样手动重新加载数据:

func reloadFetchedResultsController() {
    fetchedResultsController = createFetchedResultsController()

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

    tableView.reloadData()
}

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

问题

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

B:是否可以强制我的提取结果控制器重新加载其数据?因为,作为最后的选择,我可以在执行#3 之后强制在获取的结果控制器上重新加载,最后在此之后强制执行表视图更新,因此它将使用新的批处理重新加载其单元格,从提取的结果控制器更新数据。

1 个答案:

答案 0 :(得分: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
    }
}