NSFetchedResultsControllerDelegate 返回错误的 NSFetchedResultsChangeType

NSFetchedResultsControllerDelegate returning wrong NSFetchedResultsChangeType

这是我对委托的实现:

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    tableView.beginUpdates()
    ItemListTableViewController.logger.log("begin updates")
}

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    tableView.endUpdates()
    ItemListTableViewController.logger.log("end updates")
}

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    ItemListTableViewController.logger.log(type.rawValue, "\(indexPath) -> \(newIndexPath)")
    switch type {
    case .delete:
        self.tableView.deleteRows(at: [indexPath!], with: .automatic)
    case .insert:
        self.tableView.insertRows(at: [newIndexPath!], with: .automatic)
    case .move:
        self.tableView.moveRow(at: indexPath!, to: newIndexPath!)
    case .update:
        self.tableView.reloadRows(at: [indexPath!], with: .automatic)
    }
}

这是我得到的日志:

ItemListTableViewController >>> "perform insert start" 
ItemListTableViewController >>> "begin updates" 
ItemListTableViewController >>> 1 "nil -> Optional([0, 0])" 
ItemListTableViewController >>> 4 "Optional([0, 1]) -> Optional([0, 2])"
ItemListTableViewController >>> "end updates" 
ItemListTableViewController >>> "perform insert end" 

我正在尝试通过调用 context.insert(item) 将新项目插入上下文,并根据日志的第 3 行插入新项目。并且控制器根据第 4 行移动一些项目。但是 NSFetchedResultsChangeType 中原始值 '4' 的类型应该是 update 而不是 move.

我也测试过其他情况,当我需要更新一个项目时,它给了我一个move类型。

我对 updatemove 的含义有误吗?或者这是一个错误?

我错了。这是一个错误的代码。

我正在使用 Xcode 8.3.1

4 是 update 的枚举值; 3 是 move 的值。您可能会对被委托的两个索引路径感到困惑。 indexPath 是更新前的索引(在任何插入或删除之前),newIndexPath 是更新后的索引。不幸的是,关于如何使用 fetchedResults 控制器进行更新,Apple 的文档是错误的。对于更新,您应该使用 newIndexPath 因为更新是在插入和删除之后处理的。因此,您可能会遇到崩溃或不良行为。

另外你处理move的方式也不对。参见

感谢@Joe Rose,我明白这是如何工作的。我不处理可能同时插入和移动的任务,所以我没有使用你的解决方案。

我错了 updatemove 的意思。所以我再次阅读了苹果的文档。它说:

Changes are reported with the following heuristics:

  • On add and remove operations, only the added/removed object is reported.

    It’s assumed that all objects that come after the affected object are also moved, but these moves are not reported.

  • A move is reported when the changed attribute on the object is one of the sort descriptors used in the fetch request.

    An update of the object is assumed in this case, but no separate update message is sent to the delegate.

  • An update is reported when an object’s state changes, but the changed attributes aren’t part of the sort keys.

所以报move的时候,也会报update。那时,获取的对象更新了,但 tableView 没有更新,所以我需要使用 indexPath 来定位单元格,并使用 newIndexPath 来获取合适的对象。

这是我的最终解决方案,目前表现良好:

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    ItemListTableViewController.logger.log(type.rawValue, "\(indexPath) -> \(newIndexPath)")

    switch type {
    case .insert:
        self.tableView.insertRows(at: [newIndexPath!], with: .automatic)
    case .delete:
        self.tableView.deleteRows(at: [indexPath!], with: .automatic)
    case .move:
        self.tableView.moveRow(at: indexPath!, to: newIndexPath!)
        fallthrough
    case .update:
        self.configure(cell: tableView.cellForRow(at: indexPath!) as! ItemInfoCell, at: newIndexPath!)
    }
}


func configure(cell: ItemInfoCell, at indexPath: IndexPath) {
    let item = fetchController.object(at: indexPath)
    cell.item = item
}

如果有什么问题,请告诉我。