December 21, 2019

Cache an image with Swift

Cache an image with Swift

Caching images is one of those things that every iOS developer should know how to do, even if it is at the most basic level.

Why and when do we need caching?

Why

It is becoming more and more important to provide a responsive and efficient experience for your users.

Caching is one of the tools that can allow you to do this. Instead of refetching data from a web service, or doing some kind of real time processing that only needs to be done once, caching will let you store or manage those resource in a more performant/efficient way.

When

Example 1:

Let's start with refetching data from a web service. Often times when we consume an api there will be images involved. If you think about an app like Instagram, most of their content would be images.

This is where caching becomes incredibly useful. Being able to store those images on the users device in memory will prevent you from using data to fetch the image every time you need to display it to the user, but it will also improve the user experience because you can load the image from memory.

Example 2:

Another situation where caching is very useful is when you are doing some cpu/gpu intensive processing. You might have a work load that has to do a certain calculation, or apply a filter to an image. Instead of applying the filter every time you need to display the image, you can cache the filtered version of the image so that there is no processing that needs to be done. This can also save a users battery life dramatically depending on the workloads that you need to do.

Use a Dictionary as an image cache

A Dictionary can be used as a cache, but it is not recommended. Apple has a caching mechanism built in, but before we look at that let's look at how a dictionary can be used to cache images.

Using a dictionary as a cache is incredibly straight forward. I have created a new app and I have updated my ViewController to look like the following:

class ViewController: UIViewController {
    
    // 1
    var dictionaryImageCache: Dictionary<String, UIImage> = [String:UIImage]()

    override func viewDidLoad() {
        super.viewDidLoad()
    
        // 2
        self.dictionaryImageCache["testKey"] = UIImage()
    }
}
  1. Using a Dictionary where the key is a String and the value will be a UIImage.
  2. Creating a new instance of UIImage and assigning it to testKey on the Dictionary.

That is all it takes to create a very simple cache. With this you would not need to re-download the image because it would be in the Dictionary and whenever you need to retrieve the image you will use the key that you assigned the image to.

Use NSCache as an image cache

NSCache is a key-value mutable collection. NSCache has a few special features that help with being a cache. Even though a Dictionary can be used, it shouldn't be used for cache purposes.

The following features are what makes NSCache different from using something like a Dictionary:

  • NSCache has built in functionality that will ensure that the cache does not use too much system memory. If the system needs memory, NSCache will remove some items from cache in order to reduce the amount of memory being used.
  • NSCache allows you to add, remove and query items in the cache from different threads. This means that you do not need to lock the cache manually.
  • Last but not least, NSCache does not copy the key objects that are put in it, which NSMutableDictionary does.
Apple documentation on NSCache

I have updated the code to use NSCache:

class ViewController: UIViewController {
    
    // 1
    var nsCache = NSCache<NSString, UIImage>()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 2
        self.nsCache.setObject(UIImage(),
                               forKey: "testKey")
    }
}

Using NSCache is very similar to using a Dictionary:

  1. We create a new instance of NSCache. NSCache requires us to use an NSString for this example as the key needs to be a class not a struct.
  2. To set an object on NSCache we need to use the setObject method. This method will take two parameters, each parameter type is based on the types that we initialised the NSCache with. In our case, UIImage and NSString.

In order to access an object stored in the NSCache you can do the following:

self.nsCache.object(forKey: "testKey")

Conclusion

Caching is an incredibly useful tool that developers have in the toolbox, make sure to use them when possible and when it makes sense.

Also, make sure to use NSCache over a simple dictionary if you need a key-value cache.

The full source code is available here.

If you want to learn more about caching in swift, John Sundell has a good article on it.