Using async/await for ImageCache implementation

This commit is contained in:
Shadowfacts 2023-01-31 09:56:13 -05:00
parent ebfd8b3efd
commit 29596180a1
1 changed files with 50 additions and 45 deletions

View File

@ -32,46 +32,38 @@ class ImageCache {
} }
func get(_ url: URL, loadOriginal: Bool = false, completion: ((Data?, UIImage?) -> Void)?) -> Request? { 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, if !ImageCache.disableCaching,
let entry = try? cache.get(key, loadOriginal: loadOriginal) { let entry = try? cache.get(url.absoluteString, loadOriginal: loadOriginal) {
wrappedCompletion?(entry.data, entry.image) completion?(entry.data, entry.image)
return nil return nil
} else { } else {
let task = dataTask(url: url, completion: wrappedCompletion) return Task.detached(priority: .userInitiated) {
task.resume() let result = await self.fetch(url: url)
return task 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?) { func get(_ url: URL, loadOriginal: Bool = false) async -> (Data?, UIImage?) {
// todo: this should integrate with the task cancellation mechanism somehow if !ImageCache.disableCaching,
return await withCheckedContinuation { continuation in let entry = try? cache.get(url.absoluteString, loadOriginal: loadOriginal) {
_ = get(url, loadOriginal: loadOriginal) { data, image in return (entry.data, entry.image)
continuation.resume(returning: (data, 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 } guard !ImageCache.disableCaching else { return }
if !((try? cache.has(url.absoluteString)) ?? false) { if !((try? cache.has(url.absoluteString)) ?? false) {
let task = dataTask(url: url, completion: nil) Task.detached(priority: .medium) {
task.resume() _ = await self.fetch(url: url)
}
} }
} }
private func dataTask(url: URL, completion: ((Data?, UIImage?) -> Void)?) -> URLSessionDataTask { private func fetch(url: URL) async -> FetchResult {
return URLSession.shared.dataTask(with: url) { data, response, error in guard let (data, _) = try? await URLSession.shared.data(from: url) else {
guard error == nil, return .none
let data else {
return
} }
let image = UIImage(data: data) guard let image = UIImage(data: data) else {
try? self.cache.set(url.absoluteString, data: data, image: image) try? cache.set(url.absoluteString, data: data, image: nil)
completion?(data, image) 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? { func getData(_ url: URL) -> Data? {
@ -114,6 +113,12 @@ class ImageCache {
return cache.disk?.getSizeInBytes() return cache.disk?.getSizeInBytes()
} }
typealias Request = URLSessionDataTask typealias Request = Task<Void, Never>
enum FetchResult {
case data(Data)
case dataAndImage(Data, UIImage)
case none
}
} }