Episode #307

Codable as a Caching Layer - Part 2

24 minutes
Published on October 20, 2017

This video is only available to subscribers. Get access to this video and 572 others.

In this episode we continue with our caching example, this time introducing a new type that will handle the caching for us, as well as wrapping the response type into a new type that will indicate to the view controller if the response was served from the cache or from the network. We end the episode by implementing Equatable so that our store can avoid needless double callbacks if the data has not changed.

Episode Links

  • Part 1 is here
  • Sourcery - You can use this tool to automate == implementations, Equatable conformance, etc for all of your custom types
  • NSScreencast episode on Sourcery
  • SE-0143 Conditional Conformance - In the episode we got tripped up by the fact that Array<T> is not considered Equatable even if T is. We got around this by just wrapping the array with our own type and deferring to the == implementation we get from array for free. This document describes the issue in detail and suggests a change to the language to add this behavior. This proposal has been accepted and will likely be in Swift 5 (possibly even Swift 4.1).

A New Responsibility

I don't want to muddy up the API client with details about saving to disk (that would violate SRP), so I introduce a new role in the system, one that can fetch new data and save the data. I decided to call this a Store.

struct RepositoryStore {
    let client: GitHubClient
    let cache = RepositoryCache()

    init(client: GitHubClient = GitHubClient.shared) {
        self.client = client
    }

   ...
}

We're also using an new type here called RepositoryCache. This type is a simple subclass of LocalJSONStore<T> that provides the T and the filename:


class RepositoryCache : LocalJSONStore<RepositoryList> {
    init() {
        super.init(storageType: .cache, filename: "repos.json")
    }
}

Now the view controller is only concerned with dealing with the store. It will not use the API client directly:

class RepositoryListViewController: UITableViewController {

    let store = RepositoryStore()

    @objc private func fetchRepositories(forceRefresh: Bool = false) {
        store.getRepositories(forceRefresh: forceRefresh) { result in ... }
    }

Implementing CacheableOf

To give our view controllers an idea of where the data came from, we can introduce a new type that will wrap our responses:

enum ResponseType {
    case cached
    case fresh
}

struct CacheableOf<T : Equatable> {
    let object: T
    let responseType: ResponseType
}

func Cached<T>(_ object: T) -> CacheableOf<T> {
    return CacheableOf(object: object, responseType: .cached)
}

func Fresh<T>(_ object: T) -> CacheableOf<T> {
    return CacheableOf(object: object, responseType: .fresh)
}

In addition to the type, I've added some syntax-sugar to make it easy to wrap a value and return an instance of our new type.

This episode uses Xcode 9.1-beta1, Swift 4.