如何用Swift async/await模拟可同步/异步返回的CompletionHandler风格代码
Swift并发中用async/await实现带缓存的图片加载
你完全不需要拆分同步和异步两个方法,一个async函数就能实现和原来CompletionHandler完全一致的逻辑——同步检查缓存、有就立即返回,无则异步加载后更新UI,还能顺便解决线程安全问题。
核心原理
Swift的async函数并非强制异步执行:如果函数内部没有遇到await挂起点,它会同步执行当前上下文的代码。也就是说,当你在主线程调用async函数时,检查缓存的逻辑会直接在主线程完成,有缓存就立刻返回结果,完全不会有线程切换的开销。
正确实现(带线程安全)
用Actor管理缓存是Swift并发里最安全的方式,避免多线程读写缓存的竞态条件:
actor ImageCache { private var cachedImages: [URL: UIImage] = [:] func fetchImage(url: URL) async throws -> UIImage { // 同步检查缓存:当前在调用者上下文执行(主线程调用就在主线程) if let image = cachedImages[url] { return image } // 异步加载:遇到await自动挂起,切换到后台线程执行网络请求 let (data, _) = try await URLSession.shared.data(from: url) guard let image = UIImage(data: data) else { throw NSError(domain: "ImageLoader", code: 1, userInfo: [NSLocalizedDescriptionKey: "图片解析失败"]) } // 存入缓存(Actor自动保证线程安全) cachedImages[url] = image return image } }
在UICollectionView中调用
在主线程的cellForItemAt方法里直接使用,效果和原来的CompletionHandler完全一致:
// 全局或类内初始化缓存实例 let imageCache = ImageCache() func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell let imageUrl = yourImageUrls[indexPath.item] // 先设置占位图(可选,优化体验) cell.imageView.image = UIImage(named: "placeholder") // 启动Task处理异步逻辑 Task { [weak cell] in do { let image = try await imageCache.fetchImage(url: imageUrl) // 回到主线程更新UI await MainActor.run { cell?.imageView.image = image } } catch { print("图片加载失败:\(error)") await MainActor.run { cell?.imageView.image = UIImage(named: "error") } } } return cell }
为什么这能解决你的问题
- 缓存命中时:
fetchImage会同步返回图片,Task里的await瞬间完成,立刻执行MainActor.run更新UI,完全不会有延迟或闪烁,和原来的同步回调效果一样。 - 缓存未命中时:自动切换到后台执行网络请求,完成后回到主线程更新UI,和原来的异步逻辑一致。
- 不需要拆分同步/异步方法,代码更简洁,还通过Actor解决了原来代码中潜在的线程安全问题。
内容的提问来源于stack exchange,提问作者No No
相关产品推荐
相关产品推荐

