13

我正面临着一场噩梦般的performBatchUpdates崩溃collection view

问题基本上是这样的:我在服务器上的目录中有很多图像。想在collection view. 但缩略图必须从服务器异步下载。当它们到达时,它们将被插入到集合视图中,使用如下所示:

dispatch_async(dispatch_get_main_queue(),
             ^{
               [self.collectionView performBatchUpdates:^{

                 if (removedIndexes && [removedIndexes count] > 0) {
                   [self.collectionView deleteItemsAtIndexPaths:removedIndexes];
                 }

                 if (changedIndexes && [changedIndexes count] > 0) {
                   [self.collectionView reloadItemsAtIndexPaths:changedIndexes];
                 }

                 if (insertedIndexes && [insertedIndexes count] > 0) {
                   [self.collectionView insertItemsAtIndexPaths:insertedIndexes];
                 }

               } completion:nil];
             });

问题是这个(我认为)。假设在时间 = 0 时,集合视图有 10 个项目。然后我再向服务器添加 100 个文件。应用程序看到新文件并开始下载缩略图。随着缩略图的下载,它们将被插入到集合视图中。但是因为下载可能需要不同的时间,而这个下载操作是asynchronous,iOS 将在某一时刻失去对集合有多少元素的跟踪,整个事情都会因为这个灾难性的臭名昭著的消息而崩溃。

*** 由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“无效更新:第 0 节中的项目数无效。更新后现有节中包含的项目数 (213) 必须等于项目数更新前包含在该节中的项目数 (154),加上或减去从该节插入或删除的项目数(40 个插入,0 个删除),加上或减去移入或移出该节的项目数(0 移入, 0 移出)。

我有一些可疑的证据是,如果我打印数据集上的项目数,我会看到准确的 213。因此,数据集匹配正确的数字并且消息是无意义的。

我以前遇到过这个问题,但那是一个 iOS 7 项目。不知何故,现在在 iOS 8 上返回了问题,并且那里的解决方案不起作用,现在数据集IS IN SYNC

4

4 回答 4

7

听起来您需要做一些额外的工作来批量处理每个动画出现的图像group。从以前处理这样的崩溃开始,工作方式performBatchUpdates

  1. 在调用您的块之前,它会仔细检查所有项目计数并通过调用保存它们numberOfItemsInSection(这是错误消息中的 154)。
  2. 它运行块,跟踪插入/删除,并根据插入和删除计算项目的最终数量。
  3. 运行该块后,它会在询问您的数据源numberOfItemsInSection(这是 213 数字)时仔细检查计算出的计数与实际计数。如果不匹配,它将崩溃。

根据您的变量insertedIndexeschangedIndexes,您正在根据来自服务器的下载响应预先计算需要显示的内容,然后运行批处理。但是我猜你的numberOfItemsInSection方法总是只返回“真实”的项目数。

因此,如果在第 2 步中完成下载,当它在“3”中执行完整性检查时,您的号码将不再排列。

最简单的解决方案:等到所有文件都下载完毕,然后执行一个batchUpdates. 可能不是最好的用户体验,但它避免了这个问题。

更难的解决方案:根据需要执行批次,并跟踪哪些项目已经显示/当前正在与项目总数分开制作动画。就像是:

BOOL _performingAnimation;
NSInteger _finalItemCount;

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return _finalItemCount;
}

- (void)somethingDidFinishDownloading {
    if (_performingAnimation) {
        return;
    }
    // Calculate changes.
    dispatch_async(dispatch_get_main_queue(),
             ^{
                _performingAnimation = YES;
               [self.collectionView performBatchUpdates:^{

                 if (removedIndexes && [removedIndexes count] > 0) {
                   [self.collectionView deleteItemsAtIndexPaths:removedIndexes];
                 }

                 if (changedIndexes && [changedIndexes count] > 0) {
                   [self.collectionView reloadItemsAtIndexPaths:changedIndexes];
                 }

                 if (insertedIndexes && [insertedIndexes count] > 0) {
                   [self.collectionView insertItemsAtIndexPaths:insertedIndexes];
                 }

                 _finalItemCount += (insertedIndexes.count - removedIndexes.count);
               } completion:^{
                 _performingAnimation = NO;
               }];
             });
}

之后唯一要解决的问题是,如果最后一个要下载的项目在动画期间完成,请确保您对剩余项目进行最后一次检查(可能有一个performFinalAnimationIfNeeded在完成块中运行的方法)

于 2016-06-15T22:46:55.497 回答
6

对于任何有类似问题的人,让我引用以下文档UICollectionView

如果在调用此方法之前集合视图的布局不是最新的,则可能会发生重新加载。为避免出现问题,您应该在更新块内更新数据模型或确保在调用之前更新布局performBatchUpdates(_:completion:)

我最初引用了一个单独模型对象的数组,但决定在视图控制器中保留该数组的本地副本,并在performBatchUpdates(_:completion:).

问题解决了。

于 2018-12-29T05:46:03.910 回答
6

我认为问题是由索引引起的。

钥匙:

  • 对于更新和删除的项目,索引必须是原始项目的索引。
  • 对于插入的项目,索引必须是最终项目的索引。

这是一个带有注释的演示代码:

class CollectionViewController: UICollectionViewController {

    var items: [String]!

    let before = ["To Be Deleted 1", "To Be Updated 1", "To Be Updated 2", "To Be Deleted 2", "Stay"]
    let after = ["Updated 1", "Updated 2", "Added 1", "Stay", "Added 2"]

    override func viewDidLoad() {
        super.viewDidLoad()

        self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Refresh", style: .Plain, target: self, action: #selector(CollectionViewController.onRefresh(_:)))

        items = before
    }

    func onRefresh(_: AnyObject) {

        items = after

        collectionView?.performBatchUpdates({
            self.collectionView?.deleteItemsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0), NSIndexPath(forRow: 3, inSection: 0), ])

            // Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete and reload the same index path
            // self.collectionView?.reloadItemsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0), NSIndexPath(forRow: 1, inSection: 0), ])

            // NOTE: Have to be the indexes of original list
            self.collectionView?.reloadItemsAtIndexPaths([NSIndexPath(forRow: 1, inSection: 0), NSIndexPath(forRow: 2, inSection: 0), ])

            // Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert item 4 into section 0, but there are only 4 items in section 0 after the update'
            // self.collectionView?.insertItemsAtIndexPaths([NSIndexPath(forRow: 4, inSection: 0), NSIndexPath(forRow: 5, inSection: 0), ])

            // NOTE: Have to be index of final list
            self.collectionView?.insertItemsAtIndexPaths([NSIndexPath(forRow: 2, inSection: 0), NSIndexPath(forRow: 4, inSection: 0), ])

        }, completion: nil)
    }

    override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return 1
    }
    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items.count
    }

    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MyCell", forIndexPath: indexPath)

        let label = cell.viewWithTag(100) as! UILabel

        label.text = items[indexPath.row]

        return cell
    }
}
于 2016-06-15T23:50:36.890 回答
1

这可能会发生,因为您还需要确保使用 collectionViews 删除和插入部分。当您尝试在不存在的部分中插入项目时,您将遇到此崩溃。

当您在 X+1、X 处插入项目时,Preform Batch 更新不知道您打算添加第 X+1 节。而您已经添加了该节。

于 2019-06-18T21:12:05.263 回答