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

Swift并发:如何优雅实现同步返回取消Token+异步返回结果?

Swift并发实现带取消Token的图片加载函数

当然可以实现这种既能即时返回取消Token、又能异步返回图片的async/await函数,而且这种实现在实际开发中特别实用,比如列表滚动时要取消不可见单元格的图片请求,就刚好能用。

核心实现思路

把原来的闭包回调逻辑转成Swift并发风格,用withCheckedThrowingContinuation把URLSession的回调式任务适配成async/await的形式,同时生成UUID作为取消Token,把任务和Token绑定存储,再提供独立的取消方法就行。

完整代码实现

enum ImageLoadingError: Error {
    case loadingError
}

class ImageLoader {
    private var loadedImages: [URL: UIImage] = [:]
    private var requests: [UUID: URLSessionDataTask] = [:]
    // 用串行队列保证线程安全,避免多线程读写冲突
    private let syncQueue = DispatchQueue(label: "com.ImageLoader.syncQueue")
    
    func loadImage(url: URL) async throws -> (token: UUID?, image: UIImage) {
        // 先查本地缓存,命中就直接返回,Token设为nil(因为没启动异步任务,不需要取消)
        if let cachedImage = syncQueue.sync { loadedImages[url] } {
            return (nil, cachedImage)
        }
        
        let cancelToken = UUID()
        
        return try await withCheckedThrowingContinuation { continuation in
            syncQueue.async {
                let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
                    guard let self = self else { return }
                    
                    // 任务结束后,把存储的Token和任务清理掉
                    self.syncQueue.async {
                        self.requests.removeValue(forKey: cancelToken)
                    }
                    
                    // 处理任务被取消的情况
                    if let error = error as? NSError, error.code == NSURLErrorCancelled {
                        continuation.resume(throwing: error)
                        return
                    }
                    
                    // 图片加载成功的处理
                    if let data, let image = UIImage(data: data) {
                        self.syncQueue.async {
                            self.loadedImages[url] = image
                        }
                        // 回到主线程返回结果(UI相关操作通常要在主线程)
                        DispatchQueue.main.async {
                            continuation.resume(returning: (cancelToken, image))
                        }
                        return
                    }
                    
                    // 加载失败,抛出错误
                    continuation.resume(throwing: ImageLoadingError.loadingError)
                }
                
                // 把任务和对应的Token存起来,方便后续取消
                self.syncQueue.async {
                    self.requests[cancelToken] = task
                }
                task.resume()
            }
        }
    }
    
    func cancelRequest(with token: UUID) {
        syncQueue.async {
            self.requests[token]?.cancel()
            self.requests.removeValue(forKey: token)
            print("ImageLoader: cancelling request")
        }
    }
}

关键细节说明

  • 用串行队列syncQueue保护loadedImages和requests的读写,避免多线程同时操作导致的崩溃或数据错误
  • 缓存命中时返回(nil, 图片),因为没启动异步任务,自然不需要取消Token
  • 通过withCheckedThrowingContinuation把老的回调逻辑转换成async/await风格,调用方可以用try await轻松获取结果,代码更简洁
  • 取消方法通过Token精准找到对应的任务,调用cancel()并清理存储,不会影响其他任务

可行性与实用性

  • 可行:Swift并发模型支持通过元组同时返回即时生成的Token和异步得到的图片结果,完全满足需求
  • 实用:在需要手动触发取消的场景下,这种方式比依赖Task自带的取消机制更灵活——你可以通过Token精准控制单个任务的取消,不用去管理Task实例,尤其适合批量任务管理(比如列表图片加载、多资源并行加载的场景)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 20:50:31