-
-
Save alecdoconnor/6e7328d3c060ca02f17a31a7739f4075 to your computer and use it in GitHub Desktop.
// | |
// ImageCache.swift | |
// | |
// Created by Alec O'Connor on 3/6/18. | |
// Copyright © 2018 Alec O'Connor. All rights reserved. | |
// | |
import UIKit | |
class ImageCache { | |
static let shared = ImageCache() | |
private init() { } | |
let cache = NSCache<NSString, UIImage>() | |
func getImage(from url: URL, completion: @escaping ((UIImage?, URL)->())) { | |
if let image = cache.object(forKey: url.absoluteString as NSString) { | |
// Return the cached image in the completion handler | |
completion(image, url) | |
} else { | |
// The image needs to be requested from the network | |
DispatchQueue.global(qos: .userInteractive).async { | |
do { | |
let imageData = try Data(contentsOf: url) | |
if let image = UIImage(data: imageData) { | |
// Successful download, save it and return the image in the completion handler | |
self.cache.setObject(image, forKey: url.absoluteString as NSString) | |
completion(image, url) | |
} else { | |
completion(nil, url) | |
} | |
} catch { | |
completion(nil, url) | |
} | |
} | |
} | |
} | |
} |
I chose to make this its own class/singleton instead of adding as a static function in UIImageView, since it can be used in more places and should be centralized elsewhere. If you would like to use this directly on a UIImageView instance, add this extension in addition:
extension UIImageView {
func setFromCache(withURL url: URL, placeholderImage: UIImage? = nil) {
if let placeholderImage = placeholderImage {
self.image = placeholderImage
}
ImageCache.shared.getImage(fromURL: url) { [weak self] (image, url) in
if let image = image {
self?.image = image
}
}
}
}
The only caveat with setting images with a delayed completion is that you can run into a race conflict if you set different images on the same UIImageView at close times. This can happen, for example, when a user is scrolling and dequeue-able cells are sliding in and out. If a cell's UIImageView is loading, the same instance may appear in the wrong cell since it was dequeued shortly after requesting an image.
While it is not recommended to use accessibilityIdentifiers this way, you could use the following to prevent it:
extension UIImageView {
func setFromCache(withURL url: URL, placeholderImage: UIImage? = nil) {
if let placeholderImage = placeholderImage {
self.image = placeholderImage
}
self.accessibilityIdentifier = url.absoluteString
ImageCache.shared.getImage(fromURL: url) { [weak self] (image, url) in
if let image = image,
self?.accessibilityIdentifier == url.absoluteString {
// only update this UIImageView's image if it is still needed
self?.image = image
}
}
}
}
My code for a quick and efficient image cache. Usage looks like this:
I wrote this earlier this year and can't remember any specific sources. Overall, though, it's pretty generic code with a lot of potential.