Rob*_*Rob 34 async-await swift swift-concurrency
在 WWDC 2021 视频中,使用 Swift Actor 保护可变状态中,他们提供了以下代码片段:
\nactor ImageDownloader {\n private var cache: [URL: Image] = [:]\n\n func image(from url: URL) async throws -> Image? {\n if let cached = cache[url] {\n return cached\n }\n\n let image = try await downloadImage(from: url)\n\n cache[url] = cache[url, default: image]\n\n return cache[url]\n }\n\n func downloadImage(from url: URL) async throws -> Image { ... }\n}\nRun Code Online (Sandbox Code Playgroud)\n问题是 actor 提供可重入性,因此cache[url, default: image]引用有效地确保即使您由于某些竞争而执行了重复的请求,您至少在继续之后检查 actor\xe2\x80\x99s 缓存,确保您获得相同的图像对于重复的请求。
在那段视频中,他们说:
\n\n\n更好的解决方案是完全避免冗余下载。我们\xe2\x80\x99 已将该解决方案放入与该视频相关的代码中。
\n
但网站上没有与该视频相关的代码。那么,更好的解决方案是什么?
\n我了解 Actor 可重入的好处(如SE-0306中所述)。例如,如果下载四个图像,则不希望禁止重入,从而失去下载的并发性。实际上,我们希望等待对特定图像的重复先前请求的结果(如果有),如果没有,则启动一个新的downloadImage.
rob*_*off 30
Apple\xe2\x80\x99s 开发者网站现在包含 WWDC 视频的代码片段(至少在 2021 年及以后)。您可以在视频\xe2\x80\x99s页面上找到\xe2\x80\x9c更好的解决方案\ xe2\x80\x9d代码,方法是点击视频播放器下的\xe2\x80\x9cCode\xe2\x80\x9d选项卡并滚动下降到 \xe2\x80\x9c11:59 - 在等待后检查您的假设:更好的解决方案\xe2\x80\x9d。
\n您可以在开发者应用程序中找到\xe2\x80\x9cbetter解决方案\xe2\x80\x9d\xc2\xa0code 。在开发人员应用程序中打开会话,选择“代码”选项卡,然后滚动到 \xe2\x80\x9c11:59 - 等待后检查您的假设:更好的解决方案\xe2\x80\x9d。
\n\n屏幕截图来自我的 iPad,但开发者应用程序也可以在 iPhone、Mac 和 Apple TV 上使用。(我不知道Apple TV版本是否提供了查看和复制代码的方法,不过\xe2\x80\xa6)
\n据我所知,该代码在developer.apple.com 网站上不可用,无论是在WWDC 会议的页面上还是作为示例项目的一部分。
为了方便后代,这里是苹果的代码。它与安迪·伊巴内斯 (Andy Ibanez) 极其相似:
\nactor ImageDownloader {\n\n private enum CacheEntry {\n case inProgress(Task<Image, Error>)\n case ready(Image)\n }\n\n private var cache: [URL: CacheEntry] = [:]\n\n func image(from url: URL) async throws -> Image? {\n if let cached = cache[url] {\n switch cached {\n case .ready(let image):\n return image\n case .inProgress(let task):\n return try await task.value\n }\n }\n\n let task = Task {\n try await downloadImage(from: url)\n }\n\n cache[url] = .inProgress(task)\n\n do {\n let image = try await task.value\n cache[url] = .ready(image)\n return image\n } catch {\n cache[url] = nil\n throw error\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n
Rob*_*Rob 10
在我想出最初的答案后,我偶然发现了 Andy Ibanez\xe2\x80\x99s 的文章《Understanding Actors in the New Concurrency Model》,其中他没有提供 Apple\xe2\x80\x99s 代码,但提供了一些东西受到它的启发。这个想法非常相似,但他使用枚举来跟踪缓存和待处理的响应:
\nactor ImageDownloader {\n private enum ImageStatus {\n case downloading(_ task: Task<UIImage, Error>)\n case downloaded(_ image: UIImage)\n }\n \n private var cache: [URL: ImageStatus] = [:]\n \n func image(from url: URL) async throws -> UIImage {\n if let imageStatus = cache[url] {\n switch imageStatus {\n case .downloading(let task):\n return try await task.value\n case .downloaded(let image):\n return image\n }\n }\n \n let task = Task {\n try await downloadImage(url: url)\n }\n \n cache[url] = .downloading(task)\n \n do {\n let image = try await task.value\n cache[url] = .downloaded(image)\n return image\n } catch {\n // If an error occurs, we will evict the URL from the cache\n // and rethrow the original error.\n cache.removeValue(forKey: url)\n throw error\n }\n }\n \n private func downloadImage(url: URL) async throws -> UIImage {\n let imageRequest = URLRequest(url: url)\n let (data, imageResponse) = try await URLSession.shared.data(for: imageRequest)\n guard let image = UIImage(data: data), (imageResponse as? HTTPURLResponse)?.statusCode == 200 else {\n throw ImageDownloadError.badImage\n }\n return image\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n
关键是保留对 的引用Task,如果找到,则保留await其value.
也许:
actor ImageDownloader {
private var cache: [URL: Image] = [:]
private var tasks: [URL: Task<Image, Error>] = [:]
func image(from url: URL) async throws -> Image {
if let image = try await tasks[url]?.value {
print("found request")
return image
}
if let cached = cache[url] {
print("found cached")
return cached
}
let task = Task {
try await download(from: url)
}
tasks[url] = task
defer { tasks[url] = nil }
let image = try await task.value
cache[url] = image
return image
}
private func download(from url: URL) async throws -> Image {
let (data, response) = try await URLSession.shared.data(from: url)
guard
let response = response as? HTTPURLResponse,
200 ..< 300 ~= response.statusCode,
let image = Image(data: data)
else {
throw URLError(.badServerResponse)
}
return image
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2604 次 |
| 最近记录: |