与performBatchUpdates崩溃的梦魇

Spa*_*Dog 11 iphone ios uicollectionview performbatchupdates

我在面临崩溃的噩梦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];
             });
Run Code Online (Sandbox Code Playgroud)

问题是这个(我想).假设在time = 0,集合视图有10个项目.然后我再向服务器添加100个文件.应用程序查看新文件并开始下载缩略图.随着缩略图下载,它们将被插入到集合视图中.但是因为下载可能需要不同的时间并且这个下载操作是asynchronous,iOS 有时会忘记该集合有多少元素,并且这一灾难性的臭名昭着的消息将导致整个事件崩溃.

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

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

我以前有过这样的问题,在这里但那是在iOS 7项目.不知何故,问题现在在iOS 8上返回,并且那里的解决方案无效,现在数据集IS IN SYNC.

bob*_*vil 6

听起来您需要为批处理每个动画显示的图像做一些额外的工作group。通过像以前这样处理崩溃,performBatchUpdates工作方式是

  1. 在调用块之前,它会仔细检查所有项目计数并通过调用保存它们numberOfItemsInSection(这是错误消息中的154)。
  2. 它运行块,跟踪插入/删除,以及哪些项目的最终数目计算应立足于插入和缺失。
  3. 运行该块后,当它询问您的dataSource时numberOfItemsInSection(它是213号),它会仔细检查计算得出的计数与实际计数。如果不匹配,它将崩溃。

根据insertedIndexes和变量changedIndexes,您正在根据服务器的下载响应来预先计算哪些内容需要显示,然后运行批处理。但是我猜测您的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;
               }];
             });
}
Run Code Online (Sandbox Code Playgroud)

此后唯一要解决的问题是,如果动画中最后一个要下载的项目完成了,请确保对剩余项目进行一次最终检查(也许有一种performFinalAnimationIfNeeded在完成块中运行的方法)


dic*_*hen 5

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

键:

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

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)


fun*_*ct7 5

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

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

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

问题解决了。