0

TL;博士

显然,NSFetchedResultsControllerNSManagedObjectContext通过执行NSBatchUpdateRequest. 是否可以强制NSFetchedResultsController重新加载其数据以便controller(_:didChange:at:for:newIndexPath:)调用NSFetchedResultsControllerDelegate

问题

我正在使用获取的结果控制器来了解基础数据的变化,以便在表视图发生变化时更新它。我正在采用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
        }
    }

}

NSBatchUpdateRequest#3:我用来更新一堆项目的函数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执行.NSManagedObjectContextNSBatchUpdateRequest

我尝试过的解决方法

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

func reloadFetchedResultsController() {
    fetchedResultsController = createFetchedResultsController()

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

    tableView.reloadData()
}

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

问题

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

B:是否可以强制我的获取结果控制器重新加载其数据?因为,作为最后的手段,我可​​以在执行#3之后强制重新加载获取的结果控制器,最后强制更新表格视图,因此它将使用来自获取的结果控制器的新批量更新的数据重新加载它的单元格.

4

1 回答 1

2

文档

执行批量更新请求后,您必须将更改合并到上下文中。

尝试

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
    }
}
于 2019-12-21T21:33:34.953 回答