Skip to content

Instantly share code, notes, and snippets.

@ksmandersen
Created May 30, 2017 08:00
Show Gist options
  • Save ksmandersen/81dffdb7cfc58b880726afb7480e94bc to your computer and use it in GitHub Desktop.
Save ksmandersen/81dffdb7cfc58b880726afb7480e94bc to your computer and use it in GitHub Desktop.
Haneke & Swift image caching
import UIKit
import Forbind
import ForbindExtensions
import Haneke
typealias ImageProcess = (UIImage) -> UIImage
public class ImageCache {
public static let shared = ImageCache()
private let queue = OperationQueue()
private let cache = Cache<UIImage>(name: "dk.nordiskfilm.minbio.image-cache")
public func load(from url: URL) -> Promise<Result<UIImage>> {
return load(from: url, process: nil)
}
private func load(from url: URL, process: ImageProcess? = nil) -> Promise<Result<UIImage>> {
let promise = Promise<Result<UIImage>>()
let fetch = cache.fetch(key: url.absoluteString)
// The image is in already in the cache
let _ = fetch.onSuccess { image in
promise.setValue(.ok(image))
}
// We need to download the image
let _ = fetch.onFailure { [weak self] _ in
self?.performDownload(of: url, promise: promise, process: process)
}
return promise
}
private func performDownload(of url: URL, promise: Promise<Result<UIImage>>,
process: ImageProcess? = nil) {
let downloadPromise = self.networkFetch(from: url)
downloadPromise.getValue { [weak self] result in
guard let `self` = self else { return }
switch result {
// There was an error downloading the image
case .error(let error): promise.setValue(.error(error))
// Image downloaded
case .ok(let image):
if let process = process {
// Process the image
let processPromise = self.processDownload(image: image, process: process)
processPromise.getValue { image in
self.cache.set(value: image, key: url.absoluteString)
promise.setValue(.ok(image))
}
} else {
self.cache.set(value: image, key: url.absoluteString)
promise.setValue(.ok(image))
}
}
}
}
private func processDownload(image: UIImage, process: @escaping ImageProcess) -> Promise<UIImage> {
let promise = Promise<UIImage>()
queue.addOperation {
let processedImage = process(image)
OperationQueue.main.addOperation {
promise.setValue(processedImage)
}
}
return promise
}
private func networkFetch(from url: URL) -> Promise<Result<UIImage>> {
let downloadPromise = URLSession.shared.dataTask(url)
return dispatchAsync(downloadPromise => { UIImage(data: $0.0) }, queue: .main)
}
}
import UIKit
import Forbind
class URLImageView: UIImageView {
public enum State {
case notSet
case loading
case loaded
}
private(set) var url: URL?
private(set) var state: State = .notSet
private var promise: Promise<Result<UIImage>>?
private var placeholderImage: UIImage?
public init(placeholderImage: UIImage? = nil) {
super.init(image: placeholderImage)
}
public convenience init(from url: URL, placeholder: UIImage) {
self.init(placeholderImage: placeholder)
loadImage(from: url, placeholder: placeholder)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
public func loadImage(from url: URL, placeholder: UIImage? = nil) {
self.placeholderImage = placeholder
self.image = placeholder
self.url = url
sendImageRequest()
}
public func cancelLoad() {
promise = nil
guard let placeholderImage = placeholderImage else { return }
image = placeholderImage
}
private func sendImageRequest() {
guard let url = url else { return }
promise = ImageCache.shared.load(from: url)
promise?.getValueWeak { [weak self] result in
if let image = result.okValue {
guard let imageUrl = self?.url, imageUrl == url else { return }
self?.image = image
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment