Downloading images from a URL is one of the most common things that mobile developers do. Most of the solutions have to also support image caching (both in memory and file based), so the same image is not downloaded everytime when is needed – because that’s wasteful and ineficient. Also, one of the challenges of implementing such a solution is to easily integrate it in a table/collection view feed, where the images would be downloaded lazy and all of the corresponding cells will be updated with the right image when it’s successfully downloaded from the server. In this post, we will look at a way how to implement this in a more functional reactive way, using the great RXSwift library. Before you continue with the post, please make sure you first go through Networking in Swift and Functional Reactive Programming and Table Views, because we will be using stuff from those posts.
As said, in our implementation we should support caching. So let’s start by defining the caching protocol:
The protocol contains standard methods for caching – adding and getting images from the cache. Here the key for retrieving the images from the cache is the URL of the resource. In a previous post, I was discussing how an HTTP client for networking might look like in Swift. There we’ve discussed that we can define convenience methods to the base executeRequest method, e.g. GET:
Now, let’s extend the HTTPClient, so it can support a reactive method for downloading images:
In this method, we are creating an Observable, which returns a Driver of a UIImage. A Driver is an Observable that always returns on the main thread. As you can see, the method supports outside cache, which can be both memory cache or file system based cache, with the constraint that it needs to implement the ImageCaching protocol we’ve defined earlier. This method’s implementation is pretty straightforward – we are first checking whether we have an image with the provided URL in the cache. If there is we’re immediately returning it, otherwise, we are making a GET request (with the helper GET method we’ve defined in our HTTPClient) and if the request succeeds, we are updating the cache with the image, otherwise we are returning an error. Since our GET method is synchronous, we are subscribing on the Background scheduler for executing the request, while by returning Driver, we are sure that it will return on the main thread, so we can update the UI right away.
Now let’s see how we can use this method in action. Let’s define an image cache class that will implement the caching protocol we’ve defined. For simplicity, we will use a memory cache, which would be just a dictionary with a key string and a value UIImage:
We can store the ImageCache object in the app delegate and we can define an extension of the HTTPClient in there, where we will create an rx_image method with only the image URL as a parameter:
Now the users of the method don’t need to know any details about the caching mechanism – which can be replaced easily if we decide to introduce more sophisticated two step caching mechanism in the future. So for example, if we have a view model type, which should return a driver with a resulting image, the method might look like this:
We can pass the driver from this method in the cell rendering closure (also mentioned in the previous post about reactive table views), and the cell would handle this by subscribing to the result of the driver:
The callback would be called on the main thread when the request finishes and the image cell would be updated automatically.
This was just a simple example how you can implement a basic memory caching mechanism with RXSwift. The same principle can be applied also with Futures/Promises, where instead of sending a driver to the cell, you can send a Future, whose callback will be handled within the cell. Of course, there are also easier ways, if you decide to go with AFNetworking’s category or SDWebImage, where you can tell directly to the cell’s image view to take care of downloading of the image. Personally, I’m not a fan of such solutions, since you introduce another downloading mechanism for which you don’t know anything and also, it’s not natural for a view component to be responsible for such things. It’s much cleaner when all of the requests go through the same HTTP client in the project.