PHCachingImageManager偶现无结果无报错挂起问题求助
解决PHCachingImageManager结合async/await请求图片时的挂起问题
我在使用PHCachingImageManager为图片网格视图加载缩略图时,采用了Swift async/await语法封装请求,但遇到了偶尔挂起的问题——请求既不返回结果也不报错,后续调用该函数的请求也会跟着挂起,且completion回调从未执行。相关代码如下:
class PhotoRequestManager { // Properties from class private var imageCachingManager = PHCachingImageManager() func fetchImage( asset: PHAsset, targetSize: CGSize, contentMode: PHImageContentMode ) async throws -> UIImage? { let options = PHImageRequestOptions() options.deliveryMode = .opportunistic options.resizeMode = .fast options.isNetworkAccessAllowed = true options.isSynchronous = true return try await withCheckedThrowingContinuation { [weak self] continuation in self?.imageCachingManager.requestImage( for: asset, targetSize: targetSize, contentMode: contentMode, options: options ) { image, info in /// When the hang occurs, this block never gets executed. if let error = info?[PHImageErrorKey] as? Error { continuation.resume(throwing: error) return } continuation.resume(returning: image) } } } } // From UI struct MySwiftUIView: View { @EnvironmentObject private var photosRequestManager: PhotoRequestManager var body: some View { ZStack { // Content } .task { await requestImage() } } @State private var thumbnail: Image? // 假设asset和size已定义 let asset: PHAsset let size: CGSize private func requestImage() async { guard let uiImage = try? await photosRequestManager.fetchImage( asset: asset, targetSize: size, contentMode: .aspectFill) else { return } thumbnail = Image(uiImage: uiImage) } }
核心问题分析
- 同步请求与异步continuation冲突:设置
options.isSynchronous = true后,requestImage会阻塞调用线程等待结果,但SwiftUI的.task默认在主线程执行,completion回调也需要主线程执行,最终形成死锁,导致continuation永远无法resume。 - 未处理Task取消场景:View销毁时关联的
.task会被取消,但原代码未监听取消事件,也未取消PHImage请求,导致continuation持续挂起。 - weak self导致请求未触发:当
self提前释放时,self?.imageCachingManager.requestImage不会执行,continuation没有被resume的机会。 - 回调路径覆盖不全:未处理请求被取消、资产无效等场景,导致部分情况下回调无法触发。
修复方案
1. 移除同步请求配置
删除options.isSynchronous = true,让请求以异步方式执行,避免线程阻塞:
let options = PHImageRequestOptions() options.deliveryMode = .opportunistic options.resizeMode = .fast options.isNetworkAccessAllowed = true // 移除 options.isSynchronous = true
2. 处理Task取消与请求取消
在continuation中监听Task取消事件,取消对应的PHImage请求,并确保所有路径都能resume continuation:
func fetchImage( asset: PHAsset, targetSize: CGSize, contentMode: PHImageContentMode ) async throws -> UIImage? { let options = PHImageRequestOptions() options.deliveryMode = .opportunistic options.resizeMode = .fast options.isNetworkAccessAllowed = true return try await withCheckedThrowingContinuation { [weak self] continuation in guard let self = self else { continuation.resume(throwing: NSError(domain: "PhotoRequestManager", code: -1, userInfo: [NSLocalizedDescriptionKey: "管理器已释放"])) return } // 保存请求ID用于后续取消 let requestID = self.imageCachingManager.requestImage( for: asset, targetSize: targetSize, contentMode: contentMode, options: options ) { image, info in // 处理请求被取消的情况 if let isCancelled = info?[PHImageCancelledKey] as? Bool, isCancelled { continuation.resume(throwing: CancellationError()) return } // 处理请求错误 if let error = info?[PHImageErrorKey] as? Error { continuation.resume(throwing: error) return } // 返回图片结果 continuation.resume(returning: image) } // 监听Task取消,同步取消PHImage请求 continuation.onCancel = { self.imageCachingManager.cancelImageRequest(requestID) } } }
3. 增强SwiftUI侧错误处理
不要直接用try?忽略错误,添加错误日志方便排查:
private func requestImage() async { do { if let uiImage = try await photosRequestManager.fetchImage( asset: asset, targetSize: size, contentMode: .aspectFill ) { thumbnail = Image(uiImage: uiImage) } } catch { print("图片请求失败:\(error)") } }
额外建议
- 对于网格视图,使用
PHCachingImageManager的startCachingImages(for:targetSize:contentMode:options:)提前缓存即将显示的资产,减少请求延迟。 - 避免在主线程执行耗时的图片处理,必要时用
Task.detached将转换逻辑放到后台,再切回主线程更新UI。
内容的提问来源于stack exchange,提问作者Mev
相关产品推荐
相关产品推荐

