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

从S3存储桶加载图片时UITableView滚动卡顿,异步代码返回nil如何解决?

原始代码卡顿问题解决方案

卡顿原因

你当前的实现核心问题是Data(contentsOf:)属于同步阻塞方法,默认在主线程执行时会卡住所有UI响应(包括滚动、手势操作),直到图片下载完成才会恢复。同时代码没有做缓存处理,cell复用时会重复下载同一张图片,进一步加重性能损耗。
关于dequeueReusableCell的说法是正确的:如果不使用复用机制,每次新的cell进入可视区域都会重新创建完整的cell实例,滑动列表时会频繁触发对象创建、销毁操作,占用大量CPU和内存资源,也会引发卡顿。

优化方案

  • 所有网络请求放到后台线程执行,避免阻塞主线程
  • 增加二级缓存机制:内存缓存存储最近使用的图片,避免重复下载;磁盘缓存存储已下载过的图片,APP重启后也不用重新请求
  • 处理cell复用逻辑:cell滑出可视区域时取消对应的未完成图片请求,同时每次cell复用时先设置占位图,避免出现旧图片或者空白
  • 可选优化:图片下载完成后在后台线程做下采样,把图片分辨率压缩到和UIImageView的尺寸匹配,减少渲染时的CPU开销

如果不想自行实现全套逻辑,直接使用成熟的第三方图片加载库即可,上述能力这类库都已经封装完善。


异步代码返回nil的原因与修复

问题原因

URLSession.shared.dataTaskPublisher是异步操作,你当前的代码逻辑是发起请求后立刻返回image变量,此时网络请求还没有完成,receiveValue的回调还没触发,image变量还没有被赋值,自然每次都是nil。
除此之外你还有两个隐藏问题:

  1. 没有持有sink返回的AnyCancellable实例,请求发起后task会立刻被释放,网络请求直接被取消,永远不会触发回调
  2. 没有把图片赋值操作切回主线程,直接在后台回调给UIImageView赋值会引发UI异常甚至崩溃

修复方案

不要用同步返回值的方式实现异步图片加载,改成回调/Combine绑定/async await的方式实现,示例代码如下:

回调方式示例

func loadImage(url: String, completion: @escaping (UIImage?) -> Void) -> URLSessionDataTask? {
    guard let validURL = URL(string: url) else {
        completion(nil)
        return nil
    }
    let task = URLSession.shared.dataTask(with: validURL) { data, _, error in
        guard let data = data, error == nil, let image = UIImage(data: data) else {
            DispatchQueue.main.async { completion(nil) }
            return
        }
        DispatchQueue.main.async {
            completion(image)
        }
    }
    task.resume()
    // task需要存到cell的成员变量里,cell复用时调用task.cancel()取消旧请求
    return task
}

// cell里的调用代码
cell.cellImage.image = UIImage(named: "placeholder") // 先设置占位图
cell.currentLoadTask = loadImage(url: imageUrl) { [weak cell] image in
    cell?.cellImage.image = image
}

Combine 正确写法示例

// 首先要在cell内部声明存储cancellable的变量
var cancellable = Set<AnyCancellable>()

// cell复用时的加载代码
guard let validURL = URL(string: url) else {
    cell.cellImage.image = UIImage(named: "placeholder")
    return
}
// 先取消之前的未完成请求
cell.cancellable.removeAll()
cell.cellImage.image = UIImage(named: "placeholder")

URLSession.shared.dataTaskPublisher(for: validURL)
    .receive(on: DispatchQueue.main) // 结果切回主线程处理
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("加载失败: \(error)")
        }
    }, receiveValue: { [weak cell] data, _ in
        cell?.cellImage.image = UIImage(data: data)
    })
    .store(in: &cell.cancellable) // 持有cancellable,避免请求被提前释放

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 18:00:06