In this series we will tackle the problem of optimizing network access to fetch data from the network, a common theme of networked applications. While it is certainly trivial to fetch data from a server in any modern framework or OS, optimizing the frequency of access to the network, in order to save bandwidth, battery, user frustration, amongst other things, is complex. More so if you want to reduce code duplication, ensure testability, and leave something useful (and comprehensible) for the next engineer to use.
RxRepository: Building a testable, reactive, network data repository using RxSwift (part 2)
This repository shall respect a set of constraints:
- it must be testable
- it must delegate the actual network call to clients (as in, be extensible, it can even be another type of call, but for our purposes it will be a network call)
- the caching system should be based on disk and on volatile memory, and it should optimize time consumption (read from DiskCache, if available, and cache in MemoryCache, from there on always serve from MemoryCache)
- it should allow clients to decide if they prefer cached responses, require cached responses, or require cache ignorance
- it should offer a reactive contract
Well, we want to optimize resource usage.
And we want to propagate the latest version of the resources to everybody that has any interest in them. We also want to be able to compose complex logic on top of these resources.
The bottom-line is that this type of problem is tough to handle in an elegant way. We have to choose between having boilerplate code spread all over the place, or invest heavily in complex frameworks, such as CoreData or Realm, to do part of the heavy lifting for us.
Let’s imagine that we live in an ideal place, though.
From my point of view, an ideal solution, would have the following shape, no more, no less:
- At ViewControllerA indicates that it’s interested in a set of Resource1 items and whichever updates happen on those items. Then it uses these updates to present the items in a UITableView.
- At ViewControllerB save the changes of the edited Resource1 item and have those changes propagate automatically to ViewControllerA. Since ViewControllerA will process the list updates, things will simply work.
ViewControllerA, in our case, needs to load a list of Resource1 items, right?
That’s func load(request: R) -> T.
Clients need to indicate cache preference.
OK, func load(cachePolicy: CachePolicy, request: R) -> T.
And, it needs to be notified if the list of Resource1 changes.
Fair, func load(cachePolicy: CachePolicy, request: R) -> Observable<T>.
For ViewControllerB we need to save the changes made to the Resource1 item.
OK, that’s func save(request: R, item: T).
We want a reactive contract.
Then: func save(request: R, item: T) -> Completable.
Let’s unpack a few things:
- What is CachePolicy?
- What should our func load() do?
- What should our func save() do?
- How will we cope with load() returning an [T] and save() saving a T?
CachePolicy
One can model the CachePolicy as an enum that contains 3 cases, according to what we modeled above:

load()
save()
Thoughts
Let’s move forward with the first implementation of this RxRepositoryProtocol.
Circling back to our goals, remember, we want to hide the complexity of the decision that needs to be made when deciding whether a particular network request should be sent, and then, propagating the response to whoever is interested in it.
In a nutshell:
- Our ViewControllerA requests the list of Resource1 items to our Resource1Repository
- Our Resource1Repository will check if there is a cached response for this request.
- If there is one and it has not expired, then it returns it, which means it emits on an Observable<T>
- If not then it issues a network request and caches the response, emitting on an Observable<T>
ViewControllerA receives the updates of the Resource1 list, including future saves made by ViewControllerB
I believe we have enough to model something concrete.

Let’s take a moment and criticize this.
- What happens if two clients request a load, for the same Request in quick succession? Right now two calls will be executed. If more loads are requested in concurrency, more calls are made.
- What happens to previous subscribers when we call load from another client for the same Request?
- This repository is not really caching anything.
Let’s tackle each point.
Concurrency
Concurrency can, typically, open up an avenue for hard problems to replicate, let alone solve. Luckily RxSwift and the way it executes things at runtime has an elegant way of dealing with this. Schedulers.
To make sure that there is only one thread at a time executing code in the load and save we can have a serial scheduler where we subscribeOn() and where we observeOn() in the context of our repository.
A solution to this problem is tied to the next point, the previous subscribers.
Previous Subscribers
How can we, then, notify previous subscribers of new versions of the resources? We need a special type of Observable where we can control the events it emits. We need something that’s known in Rx as a Subject.
There are multiple types of Subjects. We will use a ReplaySubject.
A ReplaySubject is simply a type of Observable that replays previous events when a client subscribes. In order to learn more about Subjects and the differences between the types of Subjects have a read here.
Let’s factor this in our RxRepositoryNetwork:
Cache
This repository isn’t caching anything. This is simply going to the network and delivering results to clients.
If there was only a way to have this neatly tied into our existing infrastructure, right?
But wait, what if we compose several instances of RxRepository? What if we create a RxRepositoryMemory that uses our MemoryCache, and then create a RxRepositoryComposite that has logic to cascade invocations of load() on our RxRepositoryMemory and on our RxRepositoryNetwork
That is very well possible. Let’s give it a try.
We need to come up with a couple of definitions first. We need to add semantics to the return values of load() -> Observable<T>, so that we can decide, based on our CachePolicy, what happened on a particular RxRepository. Picture this, we try to load request1 from RxRepositoryMemory with a .cacheElseLoad policy. If request1 is not cached in the RxRepositoryMemory this load method should indicate that there is no value. This is different of returning nil, or emitting an error on the Observable<T>. We can define something like:

To get these semantics tied in to the values emitted by our load’s returned Observable.
We would also like to reuse our RxRepositoryProtocol in our RxRepositoryComposite, so we will create a RxRepositoryBase to accommodate this. First, let’s change our RxRepositoryProtocol to include the RxRepositoryLoadResult:

Now let’s model our RxRepositoryBase:

Let’s take this and define our RxRepositoryMemory:

Finally, let’s create our RxRepositoryComposite:

Looking Back
Tests
Let’s see how we can test our RxRepositoryComposite. Since we are using RxSwift, I recommend reading this excellent article by Shai Mishali to find out the basics on how to write tests for RxSwift code.
These tests use RxNimble and RxTest. The latter is part of RxSwift.
These tests use an evolved version of our composite repository that uses a disk repository as well.
Conclusion
In part 3 we will criticize the infrastructure we have so far, by integrating it in a more concrete example, and try to understand how well this performs in a more close to real life usage. Up until now we have been in wonderland, and we all know that at some point in time we need to set our feet back in the ground.