From 29596180a120e8221f272c8c76e321b467a1a4f0 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Tue, 31 Jan 2023 09:56:13 -0500 Subject: [PATCH] Using async/await for ImageCache implementation --- Tusker/Caching/ImageCache.swift | 95 +++++++++++++++++---------------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/Tusker/Caching/ImageCache.swift b/Tusker/Caching/ImageCache.swift index f7398260..d5ec9d90 100644 --- a/Tusker/Caching/ImageCache.swift +++ b/Tusker/Caching/ImageCache.swift @@ -32,46 +32,38 @@ class ImageCache { } func get(_ url: URL, loadOriginal: Bool = false, completion: ((Data?, UIImage?) -> Void)?) -> Request? { - let key = url.absoluteString - - let wrappedCompletion: ((Data?, UIImage?) -> Void)? - if let completion = completion { - wrappedCompletion = { (data, image) in - if let image { - if !loadOriginal, - let size = self.desiredPixelSize { - image.prepareThumbnail(of: size) { - completion(data, $0) - } - } else { - image.prepareForDisplay { - completion(data, $0) - } - } - } else { - completion(data, image) - } - } - } else { - wrappedCompletion = nil - } - if !ImageCache.disableCaching, - let entry = try? cache.get(key, loadOriginal: loadOriginal) { - wrappedCompletion?(entry.data, entry.image) + let entry = try? cache.get(url.absoluteString, loadOriginal: loadOriginal) { + completion?(entry.data, entry.image) return nil } else { - let task = dataTask(url: url, completion: wrappedCompletion) - task.resume() - return task + return Task.detached(priority: .userInitiated) { + let result = await self.fetch(url: url) + switch result { + case .data(let data): + completion?(data, nil) + case .dataAndImage(let data, let image): + completion?(data, image) + case .none: + completion?(nil, nil) + } + } } } func get(_ url: URL, loadOriginal: Bool = false) async -> (Data?, UIImage?) { - // todo: this should integrate with the task cancellation mechanism somehow - return await withCheckedContinuation { continuation in - _ = get(url, loadOriginal: loadOriginal) { data, image in - continuation.resume(returning: (data, image)) + if !ImageCache.disableCaching, + let entry = try? cache.get(url.absoluteString, loadOriginal: loadOriginal) { + return (entry.data, entry.image) + } else { + let result = await self.fetch(url: url) + switch result { + case .data(let data): + return (data, nil) + case .dataAndImage(let data, let image): + return (data, image) + case .none: + return (nil, nil) } } } @@ -81,21 +73,28 @@ class ImageCache { guard !ImageCache.disableCaching else { return } if !((try? cache.has(url.absoluteString)) ?? false) { - let task = dataTask(url: url, completion: nil) - task.resume() + Task.detached(priority: .medium) { + _ = await self.fetch(url: url) + } } } - private func dataTask(url: URL, completion: ((Data?, UIImage?) -> Void)?) -> URLSessionDataTask { - return URLSession.shared.dataTask(with: url) { data, response, error in - guard error == nil, - let data else { - return - } - let image = UIImage(data: data) - try? self.cache.set(url.absoluteString, data: data, image: image) - completion?(data, image) + private func fetch(url: URL) async -> FetchResult { + guard let (data, _) = try? await URLSession.shared.data(from: url) else { + return .none } + guard let image = UIImage(data: data) else { + try? cache.set(url.absoluteString, data: data, image: nil) + return .data(data) + } + let preparedImage: UIImage? + if let desiredPixelSize { + preparedImage = await image.byPreparingThumbnail(ofSize: desiredPixelSize) + } else { + preparedImage = await image.byPreparingForDisplay() + } + try? cache.set(url.absoluteString, data: data, image: preparedImage ?? image) + return .dataAndImage(data, preparedImage ?? image) } func getData(_ url: URL) -> Data? { @@ -114,6 +113,12 @@ class ImageCache { return cache.disk?.getSizeInBytes() } - typealias Request = URLSessionDataTask + typealias Request = Task + + enum FetchResult { + case data(Data) + case dataAndImage(Data, UIImage) + case none + } }