当用户上下滚动显示这些图像的网格视图时,我使用
PHCachingImageManager
请求一系列图像的缩略图。我正在使用如下 async
函数来支持具有 async-await
风格的现代快速并发。
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?
private func requestImage() async {
guard
let uiImage = try? await photosRequestManager.fetchImage(
asset: asset, targetSize: size)
else {
return
}
thumbnail = Image(uiImage: uiImage)
}
}
我遇到了一个奇怪的问题,有时这个函数会挂起而不返回结果或错误。没有控制台错误或任何可见的内容,只是该函数从不返回值。发生这种情况时,对同一函数的任何后续请求也会挂起。如果有人知道这里可能发生什么,非常感谢任何帮助。
我不熟悉不调用
requestImage
完成处理程序的场景。 (如果有的话,您实际上遇到了相反的问题,即使用 .opportunistic
可能会导致完成处理程序被多次调用,这对于您的简单延续模式来说将是一个问题。您确实应该使用 AsyncStream
或 AsyncThrowingStream
如果处理 .opportunistic
交付模式。请参阅此答案后半部分的示例。)
您应该确认 (a)
requestImage
正在接到电话; (b) 其完成处理程序也被调用; (c) 确定问题是否确实是未调用完成处理程序闭包,或者问题是否是 ?
之一隐藏了某些问题。
有几个考虑因素:
try?
。
考虑:
guard let uiImage = try? await photosRequestManager.fetchImage(
asset: asset,
targetSize: size
) else {
return
}
如果抛出错误,
try?
将丢弃错误(及其有意义的诊断信息),默默退出并且永远不会满足继续。我建议:
try?
替换为 do
-try
-catch
;continuation.resume(throwing: …)
;nil
,记录该事实,并使用您自己的自定义错误继续恢复。self?
。
考虑:
func fetchImage(
asset: PHAsset, targetSize: CGSize, contentMode: PHImageContentMode
) async throws -> UIImage? {
let options = …
return try await withCheckedThrowingContinuation { [weak self] continuation in
self?.imageCachingManager.requestImage(…) { image, info in
…
}
}
}
在不太可能的情况下,
self
是nil
,requestImage
永远不会被调用,并且延续永远不会被满足。就个人而言,我建议不要在延续中使用[weak self]
捕获列表。
简而言之,我建议查看所有执行路径,并确保没有任何路径可以阻止继续得到满足。但现在,如果出现任何错误或
self
超出范围,您将默默地失败,并且延续将保持未解决状态。