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

如何在TableView Cell中为耗时任务实时显示Progress Bar?

耗时任务中实时显示进度条的实现方案

问题背景

执行耗时任务时,如何用Progress Bar实时展示进度?比如某个执行时间长、完成后返回True的函数,执行期间怎么实现进度条实时更新?不确定是否需要用到线程,当前函数执行时无任何输出,希望从代码层面了解具体实现方式。

现有代码示例

func callApi() {
    let group = DispatchGroup()

    for (indexVal, ref) in arrayURL.enumerated() {
        group.enter()
        let progressQueue = DispatchQueue(label: "com.alamofire.progressQueue", qos: .utility)

        AF.download(ref)
            .downloadProgress(queue: progressQueue) { progress in
                print("Download Progress: \(progress.fractionCompleted)")
                
                DispatchQueue.main.async {
                    guard let cell = self.tableProgress.cellForRow(at: NSIndexPath.init(row: indexVal, section: 0) as IndexPath) as? ProgressCell else {
                        return // or fatalError() or whatever
                    }
                    cell.progressView.progress = Float(progress.fractionCompleted)
                }
            }
            .responseData { response in
                if let data = response.value {
                    print("===============>", indexVal)
                    group.leave()
                    print(data)
                }
            }
    }
    group.notify(queue: .main, execute: { // executed after all async calls in for loop finish
        print("done with all async calls")
    })
}

核心实现思路与优化建议

1. 必须用异步线程/队列

耗时任务绝对不能放在主线程执行——主线程负责UI渲染,一旦被阻塞,界面会直接卡死,进度条根本无法实时更新。正确逻辑是:

  • 把耗时任务(下载、本地计算、文件IO等)放到后台队列执行
  • 所有更新UI的操作(包括进度条刷新)必须切回主线程执行

2. 你的下载任务代码解析

你基于Alamofire的代码已经做了正确的基础处理:

  • 用DispatchGroup管理多个下载任务的完成状态,确保所有任务结束后执行统一回调
  • 给downloadProgress指定后台队列progressQueue处理进度计算,避免占用主线程资源
  • 进度回调中通过DispatchQueue.main.async切回主线程更新Cell进度条,符合UI操作必须在主线程的规则

3. 代码优化点

  • 避免直接通过cellForRow获取Cell:当TableView滚动时,目标Cell可能被复用或滚出屏幕,cellForRow会返回nil,导致进度更新失效。建议改用数据驱动:

    1. 初始化一个和arrayURL长度一致的进度数组:var taskProgresses: [Float] = Array(repeating: 0.0, count: arrayURL.count)
    2. 进度更新时,先修改数组值:taskProgresses[indexVal] = Float(progress.fractionCompleted)
    3. 调用TableView刷新对应行:tableProgress.reloadRows(at: [IndexPath(row: indexVal, section: 0)], with: .none)
    4. 在ProgressCell中添加配置方法,比如func configure(with progress: Float),在cellForRowAt中调用该方法设置进度,适配Cell复用场景
  • 完善错误处理:当前代码仅在下载成功时调用group.leave(),如果下载失败(网络错误、文件损坏等),会导致DispatchGroup永远处于等待状态,引发内存泄漏或逻辑阻塞。建议在responseData中无论成功失败都执行group.leave():

    .responseData { response in
        defer {
            group.leave() // 无论成功失败都执行leave
        }
        if let data = response.value {
            print("===============>", indexVal)
            print(data)
        } else {
            print("下载失败:\(indexVal)")
        }
    }
    

4. 通用耗时任务(非网络)的进度实现

如果是本地耗时任务(比如大文件解析、循环计算),可以通过闭包回调传递进度:

// 定义带进度回调的耗时任务函数
func longRunningTask(totalSteps: Int, completion: @escaping (Bool) -> Void, progressUpdate: @escaping (Float) -> Void) {
    // 放到后台队列执行
    DispatchQueue.global(qos: .utility).async {
        for step in 0..<totalSteps {
            // 模拟耗时操作(替换成你的业务逻辑)
            Thread.sleep(forTimeInterval: 0.001)
            
            // 计算当前进度
            let currentProgress = Float(step + 1) / Float(totalSteps)
            
            // 切回主线程更新UI
            DispatchQueue.main.async {
                progressUpdate(currentProgress)
            }
        }
        // 任务完成,返回结果
        DispatchQueue.main.async {
            completion(true)
        }
    }
}

// 调用示例
longRunningTask(totalSteps: 1000, completion: { success in
    print("任务执行完成:\(success)")
}, progressUpdate: { progress in
    // 更新UI进度条
    self.globalProgressView.progress = progress
})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 19:45:41