You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS UICollectionView批量更新崩溃求助(NSInternalInconsistencyException)

UICollectionView批量更新偶现崩溃排查

问题背景

网络请求后尝试更新仅含1个section的UICollectionView,插入新元素,偶现崩溃。代码参考过Raywenderlich论坛及StackOverflow方案,但无法稳定复现问题。

核心更新代码

func process(_ difference: [CollectionDifference<User>.Change]?) {
        guard let difference = difference, !difference.isEmpty else {
            updateDataSource()
            collectionView.reloadData()
            return
        }
        let deleting = difference.contains { change in
            if case let .remove(offset: _, element: _, associatedWith: associatedWith) = change {
                return associatedWith == nil
            }
            return false
        }

        let inserting = difference.contains { change in
            if case let .insert(offset: _, element: _, associatedWith: associatedWith) = change {
                return associatedWith == nil
            }
            return false
        }

        isCollectionViewUpdating = true
        collectionView.performBatchUpdates({
            updateDataSource()
            for change in difference {
                switch change {
                case let .insert(offset: offset, element: recommendation, associatedWith: _):
                    collectionView.insertItems(at: [IndexPath(row: offset, section: 0)])
                case let .remove(offset: offset, element: _, associatedWith: _):
                    collectionView.deleteItems(at: [IndexPath(row: offset, section: 0)])
                }
            }
        }, completion: { [weak self] _ in
            guard let self = self else { return }
            self.isCollectionViewUpdating = false

            self.finalizingIndexPaths.removeAll()
            if deleting {
                self.paginate()
            } else if inserting {
                self.delegate?.didInsertInCollectionView()
            }
        })
    }

崩溃错误日志

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid batch updates detected: the number of sections and/or items returned by the data source before and/or after performing the batch updates are inconsistent with the updates.
Data source before updates = { 1 section with item counts: [16] }
Data source after updates = { 1 section with item counts: [16] }
Updates = [
    Insert item (0 - 0),
    Insert item (0 - 1),
    Insert item (0 - 2),
    Insert item (0 - 3),
    Insert item (0 - 4),
    Insert item (0 - 5),
    Insert item (0 - 6),
    Insert item (0 - 7),
    Insert item (0 - 8),
    Insert item (0 - 9),
    Insert item (0 - 10),
    Insert item (0 - 11),
    Insert item (0 - 12),
    Insert item (0 - 13),
    Insert item (0 - 14),
    Insert item (0 - 15)
]

相关生命周期回调代码

该UICollectionView所在视图控制器作为子控制器嵌入标签栏控制器的父控制器中,包含以下回调:

public func willBecomeActive() {
   view.layoutIfNeeded()
   collectionView.reloadData()
   // need to add this empty performBatchUpdates, otherwise the cells don't
   // come on screen in tests
   collectionView.performBatchUpdates({}, completion: nil)
}

public func didResignActive() {
    collectionView.reloadData()
}

override public func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    collectionViewFlowLayout.updateItemSize()

    // helps to correctly resizes the cells.
    UIView.performWithoutAnimation {
       collectionView.performBatchUpdates({}, completion: nil)
    }
}

问题分析与修复建议

1. 并发更新冲突

当前代码中isCollectionViewUpdating标记仅在批量更新时设置,但生命周期回调中的reloadData()和空performBatchUpdates()未判断该标记,可能在批量更新过程中触发额外的数据源刷新,导致系统校验数据源状态时出现不一致。

修复:在所有操作UICollectionView的生命周期方法中增加状态判断:

public func willBecomeActive() {
   view.layoutIfNeeded()
   guard !isCollectionViewUpdating else { return }
   collectionView.reloadData()
   collectionView.performBatchUpdates({}, completion: nil)
}

public func didResignActive() {
    guard !isCollectionViewUpdating else { return }
    collectionView.reloadData()
}

override public func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    collectionViewFlowLayout.updateItemSize()

    guard !isCollectionViewUpdating else { return }
    UIView.performWithoutAnimation {
       collectionView.performBatchUpdates({}, completion: nil)
    }
}

2. 数据源更新与批量操作的逻辑不匹配

错误日志显示:更新前后数据源数量均为16,但执行了16个insert操作,说明updateDataSource()执行后数据源未发生变化,与声明的insert操作矛盾。

可能原因及修复:

  • 检查updateDataSource()逻辑:若该方法是直接替换整个数据源而非根据difference增量更新,会导致difference中的offset与实际数据源变化不匹配。需确保updateDataSource()是根据传入的difference对旧数据源进行增量修改(插入/删除对应位置的元素)。
  • 检查CollectionDifference的生成逻辑:确认差异计算是基于旧数据源和新数据源的正确对比,且User类型的Equatable实现正确(避免误判元素重复/缺失)。

3. 批量更新的执行顺序

performBatchUpdates闭包中先调用updateDataSource()再执行insert/delete操作,虽符合常规流程,但需确保:

  • 闭包执行前,数据源处于旧状态;
  • 闭包执行后,数据源处于新状态;
  • 声明的insert/delete操作数量与新旧数据源的数量差完全一致(旧数量 + 插入数 - 删除数 = 新数量)。

若difference中包含无效的insert操作(比如元素已存在于数据源),需在处理前过滤掉这类操作,或重新计算正确的差异。


内容的提问来源于stack exchange,提问作者DesperateLearner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 20:05:34