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

如何确保TableView每个单元格的Alamofire异步请求全部完成?

解决UITableView异步请求数据的问题

这个问题其实很常见——异步请求和UITableView的cell复用机制撞在一起就容易出这类问题。咱们先拆解下你当前代码的核心问题,再一步步解决:

你当前代码的问题

  1. cell复用导致数据错乱:cellForRowAt会快速调用,当请求异步返回时,对应的cell可能已经被滚动出屏幕、复用给了其他indexPath,这时候更新cell内容就会显示到错误的行上。
  2. 请求无管理,资源浪费:滚动时会重复发起大量相同请求,而且有些请求因为cell被回收,即使返回了也无法正确更新UI,相当于做了无用功。
  3. 无加载/错误状态:用户看不到数据加载中的状态,请求失败也没有提示,体验不好。

解决方案一:给Cell添加请求生命周期管理

我们可以给自定义Cell添加一个属性来跟踪当前请求,在cell复用时取消旧请求,避免数据错乱:

第一步:自定义TableViewCell

class CustomTableViewCell: UITableViewCell {
    // 保存当前正在进行的请求
    var currentRequest: DataRequest?
    
    override func prepareForReuse() {
        super.prepareForReuse()
        // 复用前取消请求,清空状态
        currentRequest?.cancel()
        currentRequest = nil
        textLabel?.text = "加载中..."
    }
}

第二步:修改cellForRowAt方法

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
    // 注意这里要转成自定义Cell类型
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell 
    
    let itemId = items[indexPath.section][indexPath.row]
    let url = "https://........\(itemId)......" 

    // 先取消cell之前的请求
    cell.currentRequest?.cancel()
    
    // 发起新请求并绑定到cell
    let request = Alamofire.request(url).responseJSON { [weak self, weak cell] responseData in 
        // 弱引用避免循环引用,同时判断cell是否还存在
        guard let self = self, let cell = cell else { return }
        
        // 检查请求是否被取消(比如cell被复用了)
        if let error = responseData.result.error as? AFError, error.isCancelled {
            return
        }
        
        // 处理请求结果
        if let responseValue = responseData.result.value { 
            let swiftyJsonVar = JSON(responseValue) 
            let object = swiftyJsonVar["results"][0] 
            let name = object["name"].stringValue 
            
            // 额外检查:当前cell是否还对应原来的indexPath,彻底避免复用错误
            if let currentIndexPath = tableView.indexPath(for: cell), currentIndexPath == indexPath {
                cell.textLabel?.text = name 
            }
        } else {
            cell.textLabel?.text = "加载失败"
        }
    } 
    cell.currentRequest = request
    
    return cell 
}

解决方案二:用ViewModel分离请求与UI(更推荐)

把数据请求逻辑从ViewController中抽离出来,用ViewModel管理每个item的状态,这样代码更清晰,也能避免重复请求:

第一步:创建ItemViewModel

class ItemViewModel {
    let itemId: String
    var name: String?
    var isLoaded = false
    // 数据加载完成后的回调,用来通知UI刷新
    var onDataLoaded: (() -> Void)?
    
    init(itemId: String) {
        self.itemId = itemId
    }
    
    func loadData() {
        // 已经加载过就不再重复请求
        guard !isLoaded else { return }
        
        let url = "https://........\(itemId)......"
        Alamofire.request(url).responseJSON { [weak self] response in
            guard let self = self else { return }
            
            if let responseValue = response.result.value {
                let swiftyJsonVar = JSON(responseValue)
                let object = swiftyJsonVar["results"][0]
                self.name = object["name"].stringValue
                self.isLoaded = true
                // 通知UI刷新
                self.onDataLoaded?()
            }
        }
    }
}

第二步:在ViewController中使用ViewModel

// 把原来的items数组转换成ViewModel数组
var itemViewModels: [[ItemViewModel]] = []

override func viewDidLoad() {
    super.viewDidLoad()
    
    // 初始化ViewModel数组
    itemViewModels = items.map { sectionItems in
        sectionItems.map { ItemViewModel(itemId: $0) }
    }
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) 
    let viewModel = itemViewModels[indexPath.section][indexPath.row]
    
    // 根据ViewModel状态设置cell内容
    if let name = viewModel.name {
        cell.textLabel?.text = name
    } else {
        cell.textLabel?.text = "加载中..."
        // 设置加载完成后的回调,刷新对应cell
        viewModel.onDataLoaded = { [weak self] in
            tableView.reloadRows(at: [indexPath], with: .automatic)
        }
        // 发起请求
        viewModel.loadData()
    }
    
    return cell 
}

额外优化建议

  • 添加缓存:用NSCache或者类缓存思路,把请求过的数据缓存起来,下次直接读取,减少网络请求。
  • 批量预加载:如果数据量不大,可以在viewDidLoad中批量发起所有请求,完成后刷新整个TableView,这样用户体验更流畅。
  • 错误重试:给加载失败的cell添加点击重试的功能,提升容错性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:20:34