NSFetchedResultsController didChange ...在UICollectionView中委托崩溃?

时间:2017-03-18 14:53:01

标签: ios uicollectionview nsfetchedresultscontroller

我试图理解为什么我的应用程序在实现NSFetchedResultsController委托时会崩溃:

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

    contentCollectionView.performBatchUpdates({ 
        switch type {
        case .insert:
            guard let insertIndexPath = newIndexPath else { return }
            self.contentCollectionView.insertItems(at: [insertIndexPath])
        case .delete:
            guard let deleteIndexPath = indexPath else { return }
            self.contentCollectionView.deleteItems(at: [deleteIndexPath])
        case .update:
            guard let updateIndexPath = indexPath else { return }
            self.contentCollectionView.reloadItems(at: [updateIndexPath])
        case .move:
            guard let indexPath = indexPath, let newIndexPath = newIndexPath else { return }
            self.contentCollectionView.moveItem(at: indexPath, to: newIndexPath)
        }
    }) { (completed) in

    }

}

控制台显示此崩溃错误:

由于未捕获的异常'NSInternalInconsistencyException'而终止应用程序,原因:'无效更新:第0部分中的无效项目数。更新后现有部分中包含的项目数(52)必须等于更新前该部分中包含的项目数(50),加上或减去从该部分插入或删除的项目数(插入1个,删除0个),加上或减去移入或移出该部分的项目数量( 0移入,0移出)。

从数据的角度来看,我无法看到我的错误,核心数据已更新,但CollectionView崩溃如上所述。

当我重新启动应用程序时,它运行正常。

任何人都可以告诉我,我发布的代码是不是处理委托的正确方法?我能做错什么?

我想知道的另一件事是,如果我遇到此崩溃,因为我习惯使用UITableViews而不是集合视图。那就是说我没有像在TableView中那样处理“beginUpdates”或“endUpdates”。 UICollectionView没有我刚发现的那些调用,所以,我是否因为我没有正确处理CollectionView中的开始和结束更新而导致崩溃?如果是这样,那么最好的解决办法是什么?

增加: 所以,我尝试过建议的解决方案: https://gist.github.com/nor0x/c48463e429ba7b053fff6e277c72f8ec

它仍然崩溃。

如果我不使用FRC委托(因此,如果我根本没有设置FRC委托),那么应用程序不会因为我重新加载整个集合视图而崩溃。

知道我能做些什么来正确使用FRC的代表而不是崩溃?

增加:

这是另一个信息: 实际运行插入对象的BlockOperation。我把一个日志放到了块中:

                    blockOperations.append(
                    BlockOperation(block: { [weak self] in
                        dPrint("BlockOperation Insert Object: \(newIndexPath)")
                        if let this = self {
                            DispatchQueue.main.async {
                                this.collectionView!.insertItems(at: [newIndexPath!])
                            }
                        }
                    })
                )

那是在控制台中运行的,所以我知道在集合视图中调用“insert”(不是空的,此时集合视图不是空的部分),但是我得到了这个:

由于未捕获的异常'NSInternalInconsistencyException'而终止应用程序,原因:'无效更新:第0部分中的无效项目数。更新后现有部分中包含的项目数(64)必须等于项目数在更新(63)之前包含在该部分中,加上或减去从该部分插入或删除的项目数(0插入,0删除)和加或减移入或移出该部分的项目数(0移入,0搬出去。)

那么,为什么它说“...(0插入,0删除)......”,如果插入确实运行了?

增加:

所以,正如你从我上一次添加的内容中可以看到的那样,我在声称插入发生时犯了一个错误。日志 dPrint(“BlockOperation Insert Object:(newIndexPath)”)位于 DispatchQueue.main.async {之前,因此我实际上无法声明插入实际运行。然后我放了一个断点,崩溃发生在 DispatchQueue.main.async {可以执行之前,所以实际上没有插入!所以,我删除了 DispatchQueue.main.async {并离开了 this.collectionView!.insertItems(at:[newIndexPath!])没有更多的崩溃!!!

问题是,为什么?有什么想法吗?

2 个答案:

答案 0 :(得分:3)

我继续实施自己的解决方案。建议的异步解决方案不起作用,但仍然会崩溃。

这是大多数现在能够处理FRC委托更改以驱动集合视图的代码:

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    changes = []
}

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

    guard let dataSource = dataSource else { return }
    guard let collectionView = dataSource.collectionView else { return }

    guard collectionView.window != nil else { return }

    if type == NSFetchedResultsChangeType.insert {

        if collectionView.numberOfSections > 0 {

            if collectionView.numberOfItems( inSection: newIndexPath!.section ) == 0 {
                self.shouldReloadCollectionView = true
            } else {
                changes?.append(CollectionViewChange(isSection:false, type: type, indexPath: nil, newIndexPath: newIndexPath, sectionIndex:nil))
            }

        } else {
            self.shouldReloadCollectionView = true
        }
    }
    else if type == NSFetchedResultsChangeType.update {
        changes?.append(CollectionViewChange(isSection:false, type: type, indexPath: indexPath, newIndexPath:nil, sectionIndex:nil))
    }
    else if type == NSFetchedResultsChangeType.move {
        changes?.append(CollectionViewChange(isSection:false, type: type, indexPath: indexPath, newIndexPath: newIndexPath, sectionIndex:nil))
    }
    else if type == NSFetchedResultsChangeType.delete {
        if collectionView.numberOfItems( inSection: indexPath!.section ) == 1 {
            self.shouldReloadCollectionView = true
        } else {
            changes?.append(CollectionViewChange(isSection:false, type: type, indexPath: indexPath, newIndexPath: nil, sectionIndex:nil))
        }
    }
}

public func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
    guard let dataSource = dataSource else { return }
    guard let collectionView = dataSource.collectionView else { return }
    guard collectionView.window != nil else { return }
    changes?.append(CollectionViewChange(isSection:true, type: type, indexPath: nil, newIndexPath: nil, sectionIndex:sectionIndex))
}

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


    guard let dataSource = dataSource else {
        self.changes = nil
        return
    }
    guard let collectionView = dataSource.collectionView else {
        self.changes = nil
        return
    }

    guard collectionView.window != nil else { self.changes = nil; return }

    // Checks if we should reload the collection view to fix a bug @ http://openradar.appspot.com/12954582
    if (self.shouldReloadCollectionView) {
        collectionView.reloadData()
    } else {
        collectionView.performBatchUpdates({ () -> Void in
            if let changes = self.changes {
                for change in changes {
                    switch change.type {
                    case .insert:
                        if change.isSection {
                            collectionView.insertSections(NSIndexSet(index: change.sectionIndex!) as IndexSet)
                        } else {
                            collectionView.insertItems(at: [change.newIndexPath!])
                        }
                    case .update:
                        if change.isSection {
                            collectionView.reloadSections(NSIndexSet(index: change.sectionIndex!) as IndexSet)
                        } else {
                            collectionView.reloadItems(at: [change.indexPath!])
                        }
                    case .move:
                        if !change.isSection {
                            collectionView.moveItem(at: change.indexPath!, to: change.newIndexPath!)
                        }
                    case .delete:
                        if change.isSection {
                            collectionView.deleteSections(NSIndexSet(index: change.sectionIndex!) as IndexSet)
                        } else {
                            collectionView.deleteItems(at: [change.indexPath!])
                        }
                        break
                    }
                }
            }
        }, completion: { (finished) -> Void in
            self.changes = nil
        })
    }
}

deinit { changes = nil }

答案 1 :(得分:1)

uicollectionview与nsfetchedresultscontroller的行为不同。请看看它。这会对你有所帮助 https://gist.github.com/nor0x/c48463e429ba7b053fff6e277c72f8ec