I've been trying to implement a singleton to be used as a cache for photos which I uploaded to my iOS app from the web. I've attached three variants in the code below. I tried to get variant 2 working but it is causing a compiler error which I do not understand and would like to get help on what am I doing wrong. Variant 1 does the caching but I do not like the use of a global variable. Variant 3 does not do the actual caching and I believe it is because I am getting a copy in the assignment to var ic = ...., is that correct?
Any feedback and insight will be greatly appreciated.
Thanks, Zvi
import UIKit
private var imageCache: [String: UIImage?] = [String : UIImage?]()
class ImageCache {
class var imageCache: [String : UIImage?] {
struct Static {
static var instance: [String : UIImage?]?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = [String : UIImage?]()
}
return Static.instance!
}
}
class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = UIImage(data: NSData(contentsOfURL: NSURL(string: "http://images.apple.com/v/iphone-5s/gallery/a/images/download/photo_1.jpg")!)!)
//variant 1 - this code is working
imageCache["photo_1"] = imageView.image
NSLog(imageCache["photo_1"] == nil ? "no good" : "cached")
//variant 2 - causing a compiler error on next line: '@lvalue $T7' is not identical to '(String, UIImage?)'
//ImageCache.imageCache["photo_1"] = imageView.image
//NSLog(ImageCache.imageCache["photo_1"] == nil ? "no good" : "cached")
//variant 3 - not doing the caching
//var ic = ImageCache.imageCache
//ic["photo_1)"] = imageView.image
//NSLog(ImageCache.imageCache["photo_1"] == nil ? "no good" : "cached")
}
}
The standard singleton pattern is:
final class Manager {
static let shared = Manager()
private init() { ... }
func foo() { ... }
}
And you'd use it like so:
Manager.shared.foo()
Credit to appzYourLife for pointing out that one should declare it final
to make sure it's not accidentally subclassed as well as the use of the private
access modifier for the initializer, to ensure you don't accidentally instantiate another instance. See https://stackoverflow.com/a/38793747/1271826.
So, returning to your image cache question, you would use this singleton pattern:
final class ImageCache {
static let shared = ImageCache()
/// Private image cache.
private var cache = [String: UIImage]()
// Note, this is `private` to avoid subclassing this; singletons shouldn't be subclassed.
private init() { }
/// Subscript operator to retrieve and update cache
subscript(key: String) -> UIImage? {
get {
return cache[key]
}
set (newValue) {
cache[key] = newValue
}
}
}
Then you can:
ImageCache.shared["photo1"] = image
let image2 = ImageCache.shared["photo2"])
Or
let cache = ImageCache.shared
cache["photo1"] = image
let image2 = cache["photo2"]
Having shown a simplistic singleton cache implementation above, we should note that you probably want to (a) make it thread safe by using NSCache
; and (b) respond to memory pressure. So, the actual implementation is something like the following in Swift 3:
final class ImageCache: NSCache<AnyObject, UIImage> {
static let shared = ImageCache()
/// Observer for `UIApplicationDidReceiveMemoryWarningNotification`.
private var memoryWarningObserver: NSObjectProtocol!
/// Note, this is `private` to avoid subclassing this; singletons shouldn't be subclassed.
///
/// Add observer to purge cache upon memory pressure.
private override init() {
super.init()
memoryWarningObserver = NotificationCenter.default.addObserver(forName: .UIApplicationDidReceiveMemoryWarning, object: nil, queue: nil) { [weak self] notification in
self?.removeAllObjects()
}
}
/// The singleton will never be deallocated, but as a matter of defensive programming (in case this is
/// later refactored to not be a singleton), let's remove the observer if deallocated.
deinit {
NotificationCenter.default.removeObserver(memoryWarningObserver)
}
/// Subscript operation to retrieve and update
subscript(key: String) -> UIImage? {
get {
return object(forKey: key as AnyObject)
}
set (newValue) {
if let object = newValue {
setObject(object, forKey: key as AnyObject)
} else {
removeObject(forKey: key as AnyObject)
}
}
}
}
And you'd use it as follows:
ImageCache.shared["foo"] = image
And
let image = ImageCache.shared["foo"]
For Swift 2.3 example, see previous revision of this answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With