当将已删除的行作为NSFetchedResultsControllerDelegate处理时,NSTableView为什么会崩溃? [英] Why does NSTableView crash when processing deleted rows as NSFetchedResultsControllerDelegate?

查看:49
本文介绍了当将已删除的行作为NSFetchedResultsControllerDelegate处理时,NSTableView为什么会崩溃?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用的是NSTableView + CoreData + NSFetchedResultsController的相当标准的设置,相关的视图控制器为NSFetchedResultsControllerDelegate来接收更改.这是来自视图控制器的相关代码位:

I am using a fairly standard setup of NSTableView + CoreData + NSFetchedResultsController, with the relevant view controller being NSFetchedResultsControllerDelegate to receive the changes. Here are the relevant bits of code from the view controller:

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?){

    print("Change type \(type) for indexPath \(String(describing: indexPath)), newIndexPath \(String(describing: newIndexPath)). Changed object: \(anObject). FRC by this moment has \(String(describing: self.frc?.fetchedObjects?.count)) objects, tableView has \(self.tableView.numberOfRows) rows")

    switch type {
    case .insert:
        if let newIndexPath = newIndexPath {
            tableView.insertRows(at: [newIndexPath.item], withAnimation: .effectFade)
        }
    case .delete:
        if let indexPath = indexPath {
            tableView.removeRows(at: [indexPath.item], withAnimation: .effectFade)
        }
    case .update:
        if let indexPath = indexPath {
            let row = indexPath.item
            for column in 0..<tableView.numberOfColumns {
                tableView.reloadData(forRowIndexes: IndexSet(integer: row), columnIndexes: IndexSet(integer: column))
            }
        }
    case .move:
        if let indexPath = indexPath, let newIndexPath = newIndexPath {
            tableView.removeRows(at: [indexPath.item], withAnimation: .effectFade)
            tableView.insertRows(at: [newIndexPath.item], withAnimation: .effectFade)
        }
    @unknown default:
        fatalError("Unknown fetched results controller change result type")
    }
}

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    print("tableViewBeginUpdates")
    tableView.beginUpdates()
}

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    tableView.endUpdates()
    print("tableViewEndUpdates")
}

我了解即使删除了多行,我也应该能够以这种方式批处理所有更新.但是,这会导致崩溃,并且连续删除多个行.

I understand that I should be able to batch all the updates this way, even if multiple rows are deleted. However, this causes a crash with multiple deletes in a row.

这是会话的日志输出,该表最初具有四行,并且所有行均被删除:

Here is the log output from a session, with the table initially having four rows, and all of them being deleted:

tableViewBeginUpdates
Change type NSFetchedResultsChangeType for indexPath Optional([0, 2]), newIndexPath nil. Changed object: /… correct object info …/. FRC by this moment has Optional(0) objects, tableView has 4 rows
Change type NSFetchedResultsChangeType for indexPath Optional([0, 1]), newIndexPath nil. Changed object: /… correct object info …/. FRC by this moment has Optional(0) objects, tableView has 3 rows
Change type NSFetchedResultsChangeType for indexPath Optional([0, 0]), newIndexPath nil. Changed object: /… correct object info …/. FRC by this moment has Optional(0) objects, tableView has 2 rows
Change type NSFetchedResultsChangeType for indexPath Optional([0, 3]), newIndexPath nil. Changed object: /… correct object info …/. FRC by this moment has Optional(0) objects, tableView has 1 rows

最后一行会导致崩溃:

2019-05-06 22:01:30.968849 + 0300 MyApp [3517:598234] ***由于未捕获的异常'NSTableViewException'而终止应用程序,原因:'NSTableView错误插入/删除/移动第3行(numberOfRows:1).'

前三个删除碰巧是以正确"顺序报告的(具有较大索引[行号]的行首先被删除).最后一个似乎乱序"到达了,其他行似乎已经从NSTableView中消失了.

The first three deletes happen to be reported in the "right" order (rows with bigger indexes [row numbers] being deleted first). The last one arrives "out of order" and the other rows are seemingly already gone from NSTableView by this time.

首先如何从上下文中删除对象:我正在使用建议的最佳实践,即针对同一个NSPersistentContainer使用两个托管对象上下文,其中一个用于主线程中的UI,一个用于后台/网络工作.他们看着对方的变化.当同步上下文从网络接收到一些更改并将其保存并传播到视图上下文时,使用应用程序中其他位置的此方法,将触发此崩溃:

How are objects deleted from the context in the first place: I am using the recommended best practice of having two Managed Object Contexts work against the same NSPersistentContainer, one for UI work in the main thread, and one for background/network work in background. They watch each other’s changes. This crash is triggered when the sync context receives some changes from the network, saves them, and they propagate to view context, with this method elsewhere in the app:

@objc func syncContextDidSave(note: NSNotification) {
    viewContext.perform {
        self.viewContext.mergeChanges(fromContextDidSave: note as Notification)
    }
}

我是否误解了如何使用获取的结果控制器委托?我以为beginupdates/endupdates调用可确保表视图模型"在它们之间不发生变化?我该怎么办才能消除崩溃?

Have I misunderstood how to work with the fetched results controller delegate? I thought that the beginupdates/endupdates calls make sure that the "table view model" does not change between them? What should I do to eliminate the crash?

推荐答案

从fetchedResultsController更新比Apple文档说明要困难得多.当同时进行移动和插入或移动和删除时,共享的代码将导致此类错误.看来这不是您的情况所要解决的问题,但此设置也可以解决此问题.

Updating from a fetchedResultsController is more difficult than the Apple documentation states. The code that you are sharing will cause this kinds of a bug when there is a move and an insert or a move and delete at the same time. That doesn't seem to be what is going on for your case, but this setup will fix it as well.

indexPath 是应用删除和插入之前的索引; newIndexPath 是应用删除和插入之后的索引.

indexPath is the index BEFORE the deletes and inserts are applied; newIndexPath is the index AFTER the deletes and inserts are applied.

对于更新,您不在乎插入和删除之前的位置-仅在之后-因此,请使用 newIndexPath 而不是 indexPath .这样可以解决当您同时进行更新和插入(或更新和删除)并且单元格未按预期更新时可能发生的崩溃.

For updates you don't care where it was BEFORE the inserts and delete - only after - so use newIndexPath not indexPath. This will fix crashes that can happen when you an update and insert (or update and delete) at the same time and the cell doesn't update as you expect.

对于 move ,代表说的是它在插入之前从何处移出以及应该在插入和删除之后插入的位置.当您进行移动和插入(或移动和删除)时,这可能会具有挑战性.您可以通过以下方式解决此问题:将 controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:中的所有更改保存到三个不同的数组中,进行插入,删除和更新.当您得到一个 move 时,在插入数组和删除数组中都为其添加一个条目.在 controllerDidChangeContent:中,对删除数组降序和插入数组升序进行排序.然后应用更改-首先删除,然后插入,然后更新.这样可以解决当您同时移动并插入(或移动并删除)时可能发生的崩溃.

For move the delegate is saying where it moved from BEFORE the inserts and where it should be inserted AFTER the inserts and deletes. This can be challenging when you have a move and insert (or move and delete). You can fix this by saving all the changes from controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: into three different arrays, insert, delete and update. When you get a move add an entry for it in both the insert array and in the delete array. In controllerDidChangeContent: sort the delete array descending and the insert array ascending. Then apply the changes - first delete, then insert, then update. This will fix crashes that can happens when you have a move and insert (or move and delete) at the same time.

我无法解释为什么您删除了乱序的内容.在我的测试中,我一直看到删除以降序排列,插入以升序排列.不过,此设置也可以解决您的问题,因为有步骤可以对删除进行排序.

I can't explain why you have an out of order delete. In my testing I have always seen deletes are served descending and inserts are served ascending. Nevertheless this setup will fix your issues as well, as there is a step to sort the deletes.

如果有节,则还将节更改保存在数组中,然后按顺序应用更改:删除(降序),sectionDelete(降序),sectionInserts(升序),insert(升序),更新(任何顺序).栏目无法移动或更新.

If you have sections then also save the sections changes in arrays, and then apply the changes in order: deletes (descending), sectionDelete (descending), sectionInserts (ascending), inserts(ascending), updates (any order). Sections can't move or be updated.

摘要:

  1. 具有5个数组:sectionInserts,sectionDeletes,rowDeletes,rowInserts和rowUpdates

  1. Have 5 arrays : sectionInserts, sectionDeletes, rowDeletes, rowInserts, and rowUpdates

在controllerWillChangeContent中清除所有数组

in controllerWillChangeContent clear all the arrays

在controller:didChangeObject中:将indexPaths添加到数组中(move是删除和插入.更新使用newIndexPath)

in controller:didChangeObject: add the indexPaths into the arrays (move is a delete and an insert. Update uses newIndexPath)

在controller:didChangeSection中将该部分添加到sectionInserts或rowDeletes数组中

in controller:didChangeSection add the section into the sectionInserts or rowDeletes array

:对其进行如下处理:

  • 对row进行降序删除
  • 对section进行降序删除
  • sort部分插入升序
  • 对rowInserts进行升序排序

然后在一个performBatchUpdates块中将更改应用到collectionView:rowDeletes,sectionDelete,sectionInserts,rowInserts和rowUpdates依次.

then in one performBatchUpdates block apply the changes to the collectionView: rowDeletes, sectionDelete, sectionInserts, rowInserts and rowUpdates in that order.

这篇关于当将已删除的行作为NSFetchedResultsControllerDelegate处理时,NSTableView为什么会崩溃?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆