Two-tier Caching With NSCache

      No Comments on Two-tier Caching With NSCache

NSCache is a great way to cache items like images. What I wanted was to persist the cached items and at the same time have a cache that’s really easy to use. I came up with a little class that’s using NSCache as a primary cache and the file system as the secondary (that would explain the title 🙂 ).

NSCache

NSCache is a great way to cache items, especially images. It works pretty much like a dictionary. You set objects for keys and then fetch them. Objects from the cache are removed automatically by the system, based on the eviction policies. It’s a great way to keep a small memory footprint.

What I wanted to do was persist those objects on the disk and lazy load them in the NSCache when they’re requested. So I created a little class for this, DACache.

DACache

DACache is using a primary and a secondary cache. Primary cache is your standard NSCache, and the secondary is the file system. You can use the class with only the primary cache if you want. This class is meant to be used as a dictionary, therefore it’s really simple to use:

When you set your object for a key, the object is saved in the NSCache and persisted to the file system. When you request an object it first checks if it’s already loaded in NSCache and returns it. If it’s not loaded it will check the file system, load it from the file system into NSCache and then return it. The diagram below shows it much better:

Let’s dive into some code.

The Code

First, we’ll start by defining an interface of our class:

And now let’s implement the class:

The primary and secondary caches are defined at the top, they conform to the DACacheProvider interface. We’re using ‘MemoryCache’ which is using NSCache and ‘FileCache’ will be using the file system. Both caches conform to the ‘DACacheProvider’ interface and the properties are settable, so you can provide your custom implementation for them. For example, instead of using the file system cache, you could easily implement a cache that’s using Core Data.

In the subscript, we’re getting and setting values. The setter is pretty straight forward, you just set values for keys on both cache providers. In the getter, we have a bit of logic. We’re checking if the value is in the primary cache, if it’s not, we’ll check the secondary cache. If the value is in the secondary cache, we’ll save it in the primary cache, so the next fetch would be faster. If the value is not in the secondary cache, it’s not cached, so we return nil.

At the end of the class, we have a ‘clearCache’ method that will simply clear all objects from NSCache and delete all the cached files on the file system.

Memory Cache

Our primary cache provider will be a wrapper around NSCache and is actually a pretty simple class. It will conform to ‘DACacheProvider’ interface:

It’s a pretty simple interface, let’s take a look at the implementation:

We’re using NSCache here with NSString as the key and NSData as the value. We can’t use swifts String here because it’s a struct and NSCache keys/values have to conform to AnyObject so you’ll see an error ‘Type ‘String’ does not conform to protocol ‘AnyObject”, like so:

In the save method we’ll check if the value we’re trying to save is nil, if it is, we’ll remove the object from the cache.

File Cache

‘FileCache’ also conforms to the same interface and is a class we’ll use to persist objects on the disk. We’ll persist objects in the ‘Library\Caches’, we don’t really want these persisted permanently and since iOS can clear the ‘Library\Caches’ folder in case the storage space is low, this is a perfect place.

This class is just a bit more complicated than ‘MemoryCache’ but it’s still pretty simple:

There are some utility methods that I omitted here, you can find the full code on my GitHub account.

We can instantiate this class with a caches folder name and it will create a folder within the ‘Library\Caches’ directory with that name. This way you can have a very fine-grained control over your file cache, if you choose to. If you don’t care, all the files will be saved under ‘DACache’ folder.

Load and save methods might look complicated, but they’re not much different from the load and save for the ‘MemoryCache’ implementation. You load a file from a disk into a Data object and return it if it’s there. In the save method you check if the value is nil, if it is, you remove the file, otherwise you save it.

‘fileURL’ method will get you a URL to the file on the local file system, we’re using the key as the file name since keys are unique anyway. This method might be worth mentioning, so let’s see it:

We’ll be escaping the key that we pass in, just so we’re absolutely sure we get a qualified URL when we append the file name to the caches directory path. The rest of the method is uneventful 🙂

At the end of this class, we have ‘clearCache’ method that will delete the whole ‘cacheDirectory’ folder.

Example Usage

This is just an example on how you might use this class. It’s a method from one of the projects I’m working on, but it will give you an idea on how you might use it:

‘imagesCache’ is an instance of ‘DACache’. At the top of the method, I’m checking if I have cached data available. If there’s cached data I create an image out of it, otherwise I kick off a networking request and save the response in the cache when it’s finished.

Conclusion

I was working on an app recently and I wanted to cache images, so, naturally, I ended up using NSCache. I didn’t want to download all those images every time a user starts the app and NSCache will evict objects from the memory over time. So I came up with this class. I also like to keep things simple and added a subscript, just to keep my file easier. This class solved my problem pretty well and I really hope it will help you as well. You can find the ‘DACache’ on cocoapods and you can find the whole project on my GitHub account.

As always, have a nice day 🙂

Dejan.

More resources

1+

Leave a Reply