当我刷新CoreData实体时,UICollectionView + FetchResultsController有时会崩溃

时间:2017-12-07 10:00:10

标签: swift core-data nsfetchedresultscontroller flush

我有一个UICollectionView的问题,它通过FetchResultsController链接到CoreData Entity。我有一个搜索结果的屏幕,所以我需要从API获取记录,刷新核心数据实体并添加新记录。

它运行正常,但有时当我点击搜索按钮太快时,UICollection会破坏并且不再更新。事实上,当我尝试使用不再存在的元素时发生错误。

我添加了一些检查,但在100%的情况下都无济于事。

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


        blockOperations.append(BlockOperation(block: {
            if type == .insert {
                 self.ChatsCollectionView?.insertItems(at: [newIndexPath!])
            }

            if type == .delete {
                if ((indexPath?.row)! <= self.ChatsCollectionView.numberOfItems(inSection: 0) && self.ChatsCollectionView.numberOfItems(inSection: 0) != 0) {
                    self.ChatsCollectionView?.deleteItems(at: [indexPath!])
                }
            }

            if type == .update {
                if ((indexPath?.row)! <= self.ChatsCollectionView.numberOfItems(inSection: 0) && self.ChatsCollectionView.numberOfItems(inSection: 0) != 0) {
                    self.ChatsCollectionView?.reloadItems(at: [indexPath!])
                }
            }

            if type == .move {
                if let indexPath = indexPath {
                    self.ChatsCollectionView.deleteItems(at: [indexPath])
                }

                if let newIndexPath = newIndexPath {
                    self.ChatsCollectionView.insertItems(at: [newIndexPath])
                }
            }

        }))
}

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {

    ChatsCollectionView?.performBatchUpdates({

        for operation in self.blockOperations {
            operation.start()
        }

    }, completion: { (completed) in
        //self.ChatsCollectionView.reloadItems(at: self.ChatsCollectionView.indexPathsForSelectedItems!)
        // I comment this, because it cause crash 

    })
}

所以我现在没有崩溃,但有时我会收到错误并且Collection看起来已损坏(有些行是emty等等):

我不使用reloadData(),因为当我在实体中刷新数据时,它会工作异步并崩溃。

1 个答案:

答案 0 :(得分:1)

使用collectionView和tableView,如果你搞砸了,你的数据源确实与预期相符(即你插入了一行,但行数没有增加),视图将停止进行任何更新,并且可以显示空行。< / p>

从fetchedResultsController更新比Apple文档更难。当您同时进行移动和插入或移动和删除时,您共享的代码将导致此类错误。

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

对于更新,您不必关心插入和删除之前的位置 - 仅在 - 之后 - 因此请使用newIndexPath而不是indexPath。这将修复当您更新并同时插入(或更新和删除)并且单元格未按预期更新时可能发生的崩溃。

对于move,委托说明它在插入之前从哪里移动以及在插入和删除之后应该插入的位置。当您进行移动和插入(或移动和删除)时,这可能具有挑战性。您可以通过将所有更改从controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:保存到三个不同的数组中来解决此问题,插入,删除和更新(您可以使用自定义对象或字典 - 任何适合您的方法)。当你得到move时,在插入数组和删除数组中为它添加一个条目。在controllerDidChangeContent:中,删除数组降序,插入数组升序。然后应用更改 - 首先删除,然后插入,然后更新。这将解决当您进行移动并同时插入(或移动和删除)时可能发生的崩溃。

如果你有部分,那么也保存数组中的部分更改,然后按顺序应用更改:删除(降序),sectionDelete(降序),sectionInserts(升序),插入(升序),更新(任何顺序)。部分无法移动或更新。

要点:

  1. 有5个数组:sectionInserts,sectionDeletes,rowDeletes,rowInserts和rowUpdates
  2. 在controllerWillChangeContent中清除所有数组
  3. 控制器中的
  4. :didChangeObject:将indexPaths添加到数组中(move是删除和插入)
  5. 控制器中的
  6. :didChangeSection将section添加到sectionInserts或rowDeletes数组中
  7. controllerDidChangeContent中的
  8. :按如下方式处理它们:

    • sort rowDeletes descending
    • sort sectionDelete descending
    • sort sectionInserts ascending
    • sort rowInserts ascending
  9. 然后在一个performBatchUpdates块中按顺序将更改应用于collectionView:rowDeletes,sectionDelete,sectionInserts,rowInserts和rowUpdates。