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

关闭含Collection View的视图控制器时触发数组越界崩溃

解决Collection View插入数据时关闭VC导致数组越界崩溃的问题

看起来你遇到的核心问题是后台加载任务没有随着VC的销毁而停止,再加上全局数据源Global.customs的生命周期和VC不一致,导致即使VC被dismiss,后台线程还在修改数据源,进而触发Collection View的插入操作,最终引发数组越界。下面是具体的分析和解决方案:

问题根源拆解

  1. 后台任务未取消:你启动的后台循环会一直执行完8次迭代,即使VC已经被dismiss。虽然用了[weak self],但这只会让self?.insertItems()在self为nil时不执行,但后台线程仍在往Global.customs里追加数据,导致数据源和UI状态彻底脱节。
  2. 全局数据源的风险:Global.customs是全局变量,生命周期远长于VC,即使你在dismiss前清空它,后台线程后续的append操作仍会修改它,造成数据源和Collection View的预期状态不一致。
  3. 线程安全缺失:后台线程直接修改全局数组,主线程读取并计算插入的indexPath,存在数据竞争,可能导致计算出的updatedNumber和实际数据源状态不匹配,进而生成越界的indexPath。

具体解决方案

1. 给后台任务加上取消机制

使用DispatchWorkItem来包装后台任务,这样在VC被销毁时可以直接取消任务,避免后续的无效操作:

// 在VC中定义一个属性来保存后台任务
var loadDataWorkItem: DispatchWorkItem?

// 启动加载任务的代码替换为:
loadDataWorkItem = DispatchWorkItem { [weak self] in
    guard let self = self else { return }
    
    for customTitle in Global.titles.prefix(8) {
        // 检查任务是否已被取消,若取消则终止循环
        guard !self.loadDataWorkItem!.isCancelled else { break }
        
        autoreleasepool {
            let data = self.getData(name: customTitle)
            // 切换到主线程更新数据源和UI(确保线程安全)
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                Global.customs.append(data)
                self.insertItems()
            }
        }
    }
}

// 执行后台任务
DispatchQueue.global(qos: .background).async(execute: loadDataWorkItem!)

然后在VC即将销毁时取消任务:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if isBeingDismissed {
        // 取消后台任务
        loadDataWorkItem?.cancel()
        // 清空数据源并同步UI
        Global.customs.removeAll()
        collectionView.reloadData()
    }
}

2. 替换全局数据源为VC实例变量

全局变量很容易引发生命周期不匹配的问题,建议把数据源移到VC内部:

// 在VC中定义自己的数据源
private var customData: [YourDataType] = []

// 修改insertItems函数,使用实例变量
func insertItems() {
    guard let collectionView = self.collectionView, collectionView.window != nil else {
        // 视图已不在窗口上,无需更新
        return
    }
    
    let currentItemCount = collectionView.numberOfItems(inSection: 0)
    let newItemCount = customData.count
    let insertCount = newItemCount - currentItemCount
    
    guard insertCount > 0 else { return }
    
    // 生成安全的indexPath
    let indexPaths = (0..<insertCount).map {
        IndexPath(item: currentItemCount + $0, section: 0)
    }
    
    // 额外检查:确保indexPath不超过数据源范围
    guard indexPaths.last?.item ?? 0 < newItemCount else {
        collectionView.reloadData()
        return
    }
    
    collectionView.insertItems(at: indexPaths)
}

这样当VC被销毁时,customData也会被释放,后台任务即使没取消,[weak self]也会阻止无效的UI操作,彻底避免数据源和UI的脱节。

3. 强化insertItems的安全性

在执行插入操作前,先确认Collection View仍处于活跃状态,并且数据源和计算的indexPath完全匹配:

func insertItems() {
    // 检查Collection View是否还在窗口中(未被销毁)
    guard let collectionView = self.collectionView, collectionView.isHidden == false, collectionView.window != nil else {
        return
    }
    
    let currentItemCount = collectionView.numberOfItems(inSection: 0)
    let newItemCount = customData.count // 或Global.customs.count,建议用实例变量
    let insertCount = newItemCount - currentItemCount
    
    // 数据不一致时直接reload,避免插入错误
    if insertCount <= 0 || currentItemCount + insertCount > newItemCount {
        collectionView.reloadData()
        return
    }
    
    let indexPaths = (0..<insertCount).map { IndexPath(item: currentItemCount + $0, section: 0) }
    collectionView.insertItems(at: indexPaths)
}

总结

核心思路是让后台任务的生命周期和VC绑定,同时避免全局数据源带来的状态不一致问题。通过取消后台任务、使用实例变量作为数据源、强化UI操作的安全性,就能彻底解决关闭VC时的数组越界崩溃问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 11:32:49