iOS App Architecture with Coordinators and ReSwift

Complexity is one of our biggest issues in app development. New architectures and design patterns help us to write code, which is better maintainable and has less bugs. Before starting two new app projects a year ago, we took some time to look for new ways to improve our architecture. A combination of the Coordinator pattern and ReSwift framework seemed very promising. Now, a year later, I like to share our experiences.

Simon Müller
dreipol

--

Visit www.dreipol.ch to learn more about us.

The main reason for our research was the vast growing complexity of the apps we develop. The complexity becomes a serious issue because the MVC design pattern doesn’t scale well. It also brings a range of problems that may sound familiar to you:

  • Massive UIViewControllers
  • Hard to follow application flows
  • Complicate collaboration between team members
  • Difficult for new team members to understand the code
  • Fragile for bugs and it requires much time to debug
  • Hard to implement new features

We read about this new concept in a post from Ian Terrell: App Coordinators and Redux on iOS which includes ideas from Khanlou: Coordinators Redux.

I’ll give a short overview but won’t go into all the details. If you like more information I recommend reading the posts linked above or the ReSwift documentation.

Coordinator overview

Coordinators are made to manage view controllers, similar to how view controllers manage views.

  • They help to keep view controllers separate, independent and reusable
  • Can have child Coordinators, but know nothing of its parent Coordinator
  • No third party frameworks needed

ReSwift overview

ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift. ReSwift helps you to separate three important concerns of your app’s components:

  • State
  • Views
  • State Changes

ReSwift relies on a few principles:

  • The Store stores your entire app state in the form of a single data structure. This state can only be modified by dispatching Actions to the store. Whenever the state in the store changes, the store will notify all observers.
  • Actions are a declarative way of describing a state change. Actions don’t contain any code, they are consumed by the store and forwarded to reducers. Reducers will handle the actions by implementing a different state change for each action.
  • Reducers provide pure functions, that based on the current action and the current app state, create a new app state
We’re using the ReSwift concept and extend it with Coordinators.

Example: Login

A login is often required in apps and can become complex and hard to debug. A good architecture with clear separated components can improve the quality and maintainability a lot.

In our app we have three parts handled with ReSwift and Coordinators. The main AppState and the two substates LoginState and InboxState.

All of the three States have corresponding Actions, Reducers and Coordinators. These are the required additional files:

Let’s have a look at the code for the login part:

LoginState.swift

The LoginState contains only an enum representing the State of the UI.

LoginActions.swift

The LoginActions contains user actions (tapped) and asynchronous network actions. Actions should be events with a clear point in time.

LoginReducer.swift

A Reducer takes a State and an Action and turns it into a new State. No complex logic should be involved.

LoginCoordinator.swift

The LoginCoordinator is the only subscriber of the LoginState. He’s responsible for updating the UI, usually just managing the visible view controller. It’s also a good place to do application logic related to the login.

Keep in mind that every Action triggers a dispatch of a new AppState and all Substates. So you should only update the UI, if the LoginState has changes compared to the previous LoginState.

LoginTapped function

Dispatch an Action to let the UI change.

This is most of the code required.

Additionally you will need to initialize the mainStore and the AppCoordinator with the LoginCoordinator as child Coordinator, preferably in the AppDelegate.

Summary

With just a few steps we were able to bring a very clean structure into our code. It pretty much solved all issues I’ve mentioned at the beginning. Here are my pros and cons:

Pros

  • Brings clean structure and solves many issues caused by complexity
  • Simple States and Actions help to clarify what can change in the UI
  • Helps you and your team-mates to be on the same page in discussions
  • Views are just a simple representation of the State
  • Easy debugging because States can only be modified by Actions
  • Works excellent with UnitTests
  • Coordinators takes responsibilities off the view controller
  • Coordinators are the only State subscribers
  • The new code is pretty isolated from the rest of your app

Cons

  • Steeper learning curve
  • Not recommended for every app part or UI change
  • Requires some experience to make wise decisions
  • Limited usability for modal elements like UIAlertControllers

Verdict

For complex and especially dynamic applications I can recommend this architecture and we will continue using it in the future. Keep in mind though, to make wise decisions about which parts you develop with ReSwift. It won’t make much sense if you are using Storyboards or if you have a simple UINavigationController based app.

There are many good concepts and frameworks around, the hard part is to choose the one that does the right job. You may find more inspiration in this fantastic collection.

What do you think about this architecture? What are you using and can you recommend something?

We are happy and proud that two of our apps made with this architecture won awards at this years Best Of Swiss Apps Award. The AMIGOS app from which you saw the login example even won the Master Award of 2018 🎉.

I hope you enjoyed reading and happy coding!

. . .

Feel free to like this post, share it, follow me or dreipol on Social Media:

medium.com/@simon.mueller
twitter.com/dreipol
www.dreipol.ch

--

--