Unidirectional architectures over ReactiveSwift 🚀 — Part I: Redux

Since Facebook presented Flux in 2014 and it’s successor / alternative Redux was released by Dan Abramov and Andrew Clark there’s a lot of hype around unidirectional architectures in software development world. I’ve recently released initial version of my implementation of Redux over ReactiveSwift. This article makes a short (not really) summary of reasons why it’s done and why I think it’s a bit over other Redux stuff already written for Swift.

What problem do I (as well as other libs I’ll mention below) try to solve? The answer is pretty simple and obvious — state management. Nowadays apps have grown that large, that demand in predictable state management led people to creation of such kind of predictable state containers like Redux.

ℹ️ Redux in general ℹ️

Unlike server-side solutions, client-side apps need another approaches of reducing overall solution complexity. It doesn’t matter which exact platform client application is targeting — browser or mobile — it should perform really complex task of processing huge amount of events: user input, platform state changes, network events and updates. Usually an app has plenty of unobvious data flows within it: fire network request upon user input, meanwhile store some transient state in app’s storage, parse response and make another write transaction to app’s storage, enqueue another network request while first is in progress if user changes his mind with the input…Going to mobile specific things become even more crazy cause multithreading jumps into this game. Mobile developers need to bother about making UI updates only on a main thread, avoid concurrency when touching database, avoid deadlocks etc. Long story short,

Original Redux docs end up with three principles for predictable state management. I’ll name them and let you read more here, if you’re interested in their detailed disclosure from original author. These are:

Single source of truth

State is read-only

Changes are made with pure functions

I will briefly go through Swift ports implementing them:

ReSwift has been around for a while. It has something around 5k github stars and is up-to-date with all major Swift releases. Being ported closely from original JS implementation, this library adds few nasty Swift features, like generic state type, strong typing of subscribers etc.

ReduxKit was also used widely, but has been deprecated in favor of ReSwift. There are also some less known implementations like this, this and this, but they’re rather excerpts from people’s projects and not well-maintained to be used as community-proven solutions.

Overall, they suggest the following app data flow (called “unidirectional” for obvious reasons):

State is readonly and observable by view when it’s subscribed to Store updates

State is modified by pure functions called Reducer’s. Having previous state and an action emitted by view, they calculate new app’s state. Encapsulation hides reducers from view so state changes are isolated.

Simplicity. While ReSwift covers aspects of observing, subscription / unsubscription, events dispatch, thread safety, interfaces and protocols, my solution utilizes power of ReactiveSwift and is represented more or less by single Store class. All the above mentioned stuff is out of the box for me, because I use MutableProperty wrapped inside Store

Power and ease-of-use. With ReactiveSwift’s Signal and SignalProducer for State, it’s ready for simple subscribing as well as for complex map, filter, reduce, collect etc. operations to write truly declarative code with the smell of FRP

Stricter requirements to State, no requirements to outer world. What do I mean by “stricter” — no optionals in either State or Event, no wrapping/unwrapping. What do I mean by outer world requirements— no protocol conformance needed to be subscripted to State changes. And yes, no protocols with associatedtype so feel free to build any stores you like and create loosely-coupled solutions by using Dependency Injection (As a good example of DI framework I’ve used for Swift I can definitely name Swinject)

That’s pretty much it. Due to “no requirements to outer world”, this solution is pattern-agnostic so can be easily used as a model layer being a part of MVC, as a model and view-model layer as a part of MVVM, MVP, MV-Next-Big-Arch-Buzzword. The idea of simple MVVM app (exact like ReSwift example) can be found in Readme, I’ll get straight away to more complex example🌤

🌪 Weather app using Redux 🌪

Such kind of app is usually given as a test task for candidates applying for junior iOS engineer position. They are asked to do a fetch of weather data for current location using Gismeteo (or some another weather service) API. Weather forecast should be stored in some local storage (usually SQLite or CoreData are asked, however it’s 2017, app don’t have such memory limitations like it was around iPhone 3GS, there are strong alternatives like Realm so we won’t strictly require any framework/implementation).

The next part of an article is mostly a tutorial of building this app using Redux & few other tools, so if you want to skip it (danger zone: you might miss few valuable insights) — here’s the link to below part of the article and here is the link to full source code of described example.

If you’re still here… let’s dive inside tutorial!

🔨 Creating the project

This should look pretty familiar with XCode templates, let’s create a project called “Simple-Weather-App” and, for sure, check on Unit Tests checkmark.

Creating project

For sure, we’ll use CocoaPods as a dependency manager (because I think, it’s the best dependency manager, Carthage and SPM aren’t that strong & widespread like pods)

Let’s go ahead and create Podfile in the root project directory. I usually use this template for multi-target projects and have nothing against you borrow it for your own use :)

Podfile

pod install --verbose in Terminal, close XCode project and open newly created XCode workspace. We’re done with the most tricky part 🎉🎉🎉

🍥 Designing our app’s UI

Let’s focus on which data will we show to the user: this should be — date of last weather update, closest location, air temperature, real feel (if available), wind, rain etc. These data can be fetched from AccuWeather API, which we select as a data source. Let’s show current conditions and forecasts for 5 day, switchable by segmented control. Data will be shown via static table and has a horizontal pageable scroll. Let’s create new file WeatherView.swift and WeatherView.xib (I don’t normally use IB and do layout in code, but for simplicity let’s use IB). It’s layout is fairly simple:

WeatherView.xib

The screenshot shows connected outlet, dataSource and delegate for the table. I also disable safe area guides and trait variations since this view is intended to be content-only and invariant relative to size class. Table view needs cells to display content, let’s create one. We want a cell that will display our weather values, which can be in following formats (information obtained by exploring AccuWeather APIs):

{Value} {Unit} // Single line of weather data, e.g. Air temperature: 68 F

Next we’re going to add stub implementations to WeatherView to add table view cells and WeatherView on screen.

WeatherView.swift

ViewController.swift

Main.storyboard

Hit “Run” and you’ll be able to see following UI stub on Simulator

Simulator with UI stub

We’ve confirmed we have very basic UI to display what we’d like to display, let’s now add some “meat” to the project. You can browse intermediate result here

💃Models

Let’s set some goals we’d like to achieve with our model layer:

It should be JSON parseable / serializable

It should be immutable (of course!)

It should be easy to display on UI

It should represent our actual domain area (weather)

Having this in mind, let’s get straight ahead to modelling. We will create Weather.swift and write some neat and swifty (i hope!) code:

Weather.swift

We get weather model with fields we would like to display. Now it’s time to get some weather JSON and start parsing. I won’t put full JSON here since it’s pretty long, you can take a look by this link. I usually do Playground / Unit tests for parsing, so let’s go straight ahead and start our Unit tests! We will use Quick / Nimble over XCTest since they allow write more structured & human-readable unit tests. In terms of Quick, Test Suite is called “Spec”, so let’s create WeatherSpec.swift and start writing tests.

We haven’t implemented any mapping yet, so this file simply won’t compile. To make the mapping magic work, let’s utilize power of ObjectMapper. Get back to Weather.swift and implement ImmutableMappable conformance:

Weather.swift

Cmd+U… Executed 1 test, with 0 failures (0 unexpected) in 0.012 (0.016) seconds! (Honestly saying, i’ve did Cmd+U with fails like 10 times, until I’ve fixed all typos in the implementation, that’s why tests are especially useful for cases like parsing, now I’m: 1) sure that my model is parseable from sample JSON; 2) If something is changed, test will fail).

⚗️ More tests

That’s probably the most boring stuff, but I’m adding at least positive cases to check all parsed fields to make sure sample parsing is correct.

WeatherSpec.swift

Please note my extensive use of ! operator in unit tests code. Despite you’ve learned previously that force-unwrapping is very bad, I do it intentionally inside unit tests, because I want them:

To be simple and straightforward.

To fail much. They shouldn’t be error-prone like an app’s code.

Now let’s go ahead and add one more model, response and spec to our app & tests — result of Geoposition search. We need this, because AccuWeather cannot provide us with weather data for particular GPS coordinates, it does it instead for closest point we can find. Sample JSON is here, model and spec below.

Geoposition.swift

GeopositionSpec.swift

Take a look at our tests for country flag transform. Do you see how easy they are for testing? That’s because we’re testing pure functions here — functions that produce predictable output for given input and have no side effect. We’ll recall this concept again when we’ll be testing our reducers.

⁉️ Where is Redux

Patience, we’re close to adding what we’d like to have at the end, the predictable app’s state. Let’s do this right away:

AppState.swift

AppEvent.swift

AppStore.swift

The above 3 files encapsulate pretty much all of what our application state and events might be — with the rules of how to use it. Despite it should be unit-tested, I’ll use “test last” approach here just to show how easily services can get around here and how truly declarative app’s underlying functioning can be. Let’s take a look at LocationService we can build upon this store:

LocationService.swift

This class has no single method or instruction what to do. It has no modifiable state, moreover, it has no public methods to modify anything. It works only as pluggable middleware to AppStore but nevertheless functioning as usual location service you might have implemented plenty of times before. The last service thing to go is WeatherService

WeatherService.swift

I skipped network requests code from the above gist for simplicity. You can take a look at full implementation on github. Last thing to do is to initialize these singletons at some point. Let’s do this on application’s launch.

Cmd+R… And we can see very verbose log of each state change happened to the app — location permission requests, location fetches, network operations, parsing — finally coming to displayable app state we might want to show to user — fetched geoposition, fetched current conditions weather as well as 5-days forecast. We’ve just done solid model layer without any single line of UI code. This step’s result can be found here.

🔬Tests again

We shamefully forgot about tests in the above part, however we should fix it now. We’ve got one problem with our current testing approach, there’s an AppDelegate initializing all the services upon launch. This shouldn’t be the case for unit tests, since we don’t want the app to ask for geolocation, perform network requests etc. when running unit tests. We have to trick our setup a bit more, adding different TestAppDelegate class for unit tests target and removing swift’s implicit UIApplicationMain .

Test-aware app launch

Cmd+U… No store logs, only tests ones. You can browse intermediate results here

🔭Further tests

Let’s get to testing our app’s “real” business logic. It resides currently inside AppStore.swift and… is private. Doing testable import Simple_Weather_App doesn’t really help in this case, because this turns our to help only agains internal methods. We can try to test the state changes themselves, however it’s not that good idea, because we can’t set an initial state for each test case (Redux principle #2 — state is read-only).

But what will happen if we end up changing reducers to be internal, not private? Does it break any encapsulation? The short answer is “no”. There’s always some trade-off, between encapsulation and testing simplicity, however this is not the case. Exposion of AppStore’s reducers into global scope (probably) increases compile time for the module and it might become a problem upon horizontal scaling. But from design prospective, making reducers not private or even moving them out of the AppStore's type scope will not make any difference. They’re pure functions.

Keeping this in mind, I’ll move functions away from the AppStore and focus on reducer testing. Posting full testing gist here would be overkill (because it’s 575 SLOC 😂), i’ll leave a link. Ease of coverage for pure functions is a gift — powerful but often overlooked.

When it comes to testing WeatherService and LocationService we face real troubles. In non-Objective-C world, mocking is a nightmare. Subjects-under-test should be written that way, so their initialization involves dependency injection in some way — constructor injection, property injection etc. Let’s take a look at our current LocationService. It depends on AppStore (which is affordable, since it’s a “by-design foundation” for the app’s state). It also depends on a CLLocationManager (which is really unwanted, because now there’s no way to test LocationService unless provide CLLocationManager instance from without class’s scope)

Needed effort doesn’t worth it. We end up full of sorrow, leaving our two services without unit tests.

💄Tie to UI

Last but not least part of our weather app will be to display thoroughly designed state in UI. I’ll try to show you how neat & expressive ReactiveCocoa might be when it comes to reducing UIViewController boilerplate. Let’s take a look of desired design for our app. We want UI controls to show: content, loading state, switching current / forecast view for weather, update location, switch forecast days. Our storyboard now look like:

Screen in IB

Connecting outlets is not interesting, let’s focus on ViewController.setupObserving. This method makes all UI controls “alive” by assigning properties / actions to them.

Let’s go for them one-by-one:

We’re adding reactive extension weatherFeatures on WeatherView to bind appropriate property from ViewModel. This gonna be implemented in ViewModel

viewModel.isLoading should be implemented as SignalProducer<Bool, NoError> to bind to activity indicator

isEnabled(for:) is a function returning SignalProducer. This is bound to isEnabled property of UISegmentedControl

The same for title, but String, not Bool

We’re creating binding target for rightBarButtonItem and binding either activity indicator or refresh button, depending on state

🏃‍Run

Upon Run on Simulator we would need to have simulate location few times (and hit location button in the navigation bar) and get following screen results:

Run

We can see that states are processing precisely — current, forecast, ability to hit left/right in toolbar depending on current page. The app built entirely on two stores (AppStore and UIStore) and their state combinations. So the rough structure can be depicted by following chart:

App structure

As we can see, app is built from several simple responsibilities:

Store is responsible for managing state

Services are helper classes to deliver content. They communicate directly only to store

ViewModel holds UIStore responsible for managing UI state. It shouldn’t be mixed with the app’s state.

ViewModel itself applies transformation to the app state to make it ready for use in UI. It provides actions responsible for events delivery back to stores.

ViewController binds ViewModel’s actions and state signals to views.

On this chart, we see following data flows:

State (and its transformations) is propagated from bottom to top

Events are propagated from top to bottom

They build an infinite loop with single data flow, that’s why this architecture can be called unidirectional

💪Responsiveness to changes

When we talk about architectures we often judge them by one simple criteria — how easy is to make changes to ready solution. Let’s take a quick look on a few possible changes:

Make an expiration timeout for geolocation not 5 minutes, but 10 seconds — easy (1 line in reducer)

2. Make navigation between forecast days by swipe / scroll and not (okay, not only) by toolbar buttons — simple, but not easy. We need to reconsider UI layer for display because one tableview won’t be enough to provide smooth scrolling experience. The key complexity is UI layer — we have to reconsider ViewModel code to append additional UI actions, change content state delivery to ViewController and implement reactive UIScrollView behaviour.

🎁Bonus tests

What else needs tests in project? Let’s go for ViewModelSpec . You can find full-script here, I’ll describe the most vital parts in general:

We need stubs for success states (L16-L49). They will be used to setup stores for testing success logic.

We need few Equatable extensions (L221–227). They will be needed to make convenient equal matchers.

We’re covering few main parts of ViewModel's responsibility — testing controls’ enabled state producer (L53-L99), testing UIStore, which is the easiest part due to Redux Store nature (L100–L127), testing actions (L128-L164), testing rest of SignalProducers (L165-L197).

Comprehensive description of reactive tests are out of scope of this article, however you can pick up few ideas and build your own testing strategy.

🔥 Summary 🔥

🙏Thanks and where to go next

Thanks for reading down here, I hope you enjoyed & feel how to empower you project with unidirectional data flow.

We’ve come up with very basic Redux app, however it covers aspects of network, loading state, state persistence and restoration — thins that are often overlooked in iOS development. Despite Redux is overall good, please remember that there’s no “sorcerer’s stone” or “silver bullet” in software development world. It even doesn’t pretend to be — but if you’re struggling with state management and related bugs, probably that’s a high time to get an inspiration from here. I’ll put a link to a good disclaimer post from original Redux author Dan Abramov: You might not need Redux.

Also please don’t consider this example project as a “Bible” of how to do it right. After all, that’s only my vision. I’ve intentionally made simplifications with the project to focus on Redux itself, especially:

In real-world project I’d use concept of phantom types for cell identifiers, move some work to UIView / UITableViewCell extensions to stay more clear & SOLID

I’d have constants for parsing key-paths instead of literals

I’d create view layout & setup in code, because it gives a flexibility unlike when using IB. SnapKit is brilliant DSL making this possible

I would use unit tests with no host application target for them. This requires a bit more sophisticated isolation and modularization, but gets slightly better results in terms of speed & upon scaling. The idea is described here