Software-Architecture is an exciting topic. Once well considered it allows us to create amazing digital products. Quality and simplicity are my top priorities. The difficult challenge is to ensure that during a long development process.
I’m a big advocate on best practices and learning from others experiences. So I was looking for reliable solutions to ensure quality and simplicity. I stepped over different approaches in architectural patterns. VIPER, MVVM-C, MVP. I preferred VIPER because of it’s encapsulation approach. It made my ViewControllers cleaner and more understandable. But in the end it’s just an extended MVC pattern with its downsides.
MVC and the problems with it
Model-View-Controller is the most famous architectural pattern. The view is everything you see. The model is the data (e. g. Weather information from a website or notes in a local database). The controller interacts between those components. In a perfect world every component would be replaceable. So you can implement a new user-interface without touching the model-structure or the controller.
This might be very difficult to implement in iOS Applications. UIKit brought us ViewControllers. That means that “View” and “Controller” are not really separated. This is the reason why the MVC approach often turns in “Massive View Controllers” instead of a useful separation.
In MVC your data-flow is bidirectional. That means you request something and wait until you get something. A simple example would be:
Congratulations. You successfully block your current thread 🚥 until the timers are fetched. But that’s bad. We urgently have to write this asynchronously:
Nice! But we don’t life in a perfect world. What if something goes wrong?
The problem here is that our so called “View” interacts with controllers which are responsible for the API communication. If the controller logic changes, the view layer breaks, which is a sign something went wrong in separation of concerns.
A few months ago I attended at a great workshop about innovation. And the key learning was if you want true innovation, you have to think outside the box. Search for proven patterns outside your familiar scope. If you develop a productivity app, look for outstanding stuff in non-productivity apps and adopt it to your use case. Or if you’re looking for an architectural solution look outside iOS App development. You might come up with something like … Redux.
What is Redux?
Redux does separation of concerns like MVC. The difference is the dataflow and the state-management. It has a specific place where the state lives (the store) and where the state gets updated (the reducers). The dataflow is unidirectional. That means your data gets processed in one-direction. The idea behind it is that your subscribers (e. g. ViewController) only listens for updates and doesn’t manipulate the data it receives.
The state represents the state of your app. States contain substates like a navigationState (e. g. which view is currently active) and commonly one for each view of your app.
The store holds the entire App State and receives requests to change the state and calls reducer to perform the change. When a change has taken place, it notifies the subscriber associated to the state with the updated data.
Actions triggers state-changes. For example when a user starts a timer. Actions will be dispatched by the store, where the state lives.
Reducer will be notified by the store when it received an action. The reducers take the action and perform the actual state-change.
Subscribers subscribe for one specific state to which they later receive notifications. For example my timer-view will only subscribe for the timer state (e. g. timer currently running or not/set time). If a state change occurs that doesn’t change the timer-state, the subscriber won’t be called.
ReSwift is a small redux-like implementation for swift. It comes with a store and protocols to help you to easily dive into the redux world. You can integrate it into your projects using your package manager of choice https://github.com/ReSwift/ReSwift#installation.
I want to show you a simple example of how to use ReSwift. I’m going to build an app where you can set different timers and run them at the same time (e. g. One for potatoes, one for laundry).
At first we have to set up the store:
This creates a new ReSwift store with a (not yet) created reducer. We’ll do this later. You can leave the state nil. The state will be initialized when the first subscriber registers itself to the store.
States are simple structs containing data. They have to be marked with the “StateType” protocol.
Here you can see that I already have a sub-state. It represents all timers created in my app and is really simple.
At first we have to set up our AppReducer. This one is responsible for calling all reducers for the substates.
Our appReducer now calls the timersReducer. The timersReducer takes actions and modifies the TimersCollectionState. For example when the user creates a new timer:
After the reducer processed the action and changed the state, the subscriber will be called.
Subscriber listen for state changes. While all reducers will be called when a action gets dispatched, the subscriber will only be called when an actual state change happens.
We can implement a subscriber using the StoreSubscriber protocol. It forces us to implement the “newState” method which has the specific state as parameter.
In order to receive notifications from the store, we have to register the subscriber. Keep in mind that we only subscribe for the “timersCollectionState”. The subscriber won’t be called on other state-changes.
Nice. We’ve just created a simple working example. If you want to play around with a real Xcode Project I recommend the CounterExample: https://github.com/ReSwift/CounterExample
Network Requests 🌎
For so long things were pretty easy. But how do we trigger actions when we need data from an API? This is a common use-case in most apps. The answer is: Using Action Creators.
In this case we would dispatch fetchTimersAction(). The action itself will never be dispatched because it always returns nil. Inside the Action Creator the loadTimersAction will be dispatched when we successfully receive the data from the backend system.
The TimersState contains a service to retrieve all timers:
Action-Creators extract business logic out of view-controllers and ensure that no action gets dispatched unnecessarily.
I implemented ReSwift already in an “in-production” project and however it was a time consuming migration, it made the code structure easier and allows us to make faster changes and new implement new features easily.
One last note: This should be a brief overview over the architecture and ReSwift. If you like the approach I highly recommend the talk by Benjamin Encz, the founder of ReSwift https://academy.realm.io/posts/benji-encz-unidirectional-data-flow-swift/ and the getting started guide: https://reswift.github.io/ReSwift/master/getting-started-guide.html
Do you have questions? Feel free to reach out on twitter https://www.twitter.com/jbrunhuber