Unidirectional Data Flow in iOS Apps

A way to improve any existing architecture

Artiom Khalilyaev
Level Up Coding

--

Photo by Hunter Harritt on Unsplash

All of us might face a situation, where current architecture turns into a complete mess. In a perfect world, we would rewrite our code base with a new architectural pattern. But, we are not in a perfect world, and we don’t have time for it. The right decision to make is to try to improve an existing code base without making global changes. On my current project, my team and I have done it using a Unidirectional Data Flow (UDF) approach.

Unidirectional Data Flow

Let’s start from the short description of UDF.

The main concept of the UDF is that data moves only in a one direction: from the Model to the UI. UI is not allowed to update it itself. All what UI can do is send events to the Model, and after receiving a new event, the Model sends updated state to the UI. The state that UI uses for updates has to be immutable, since UI uses it only for updates. UI are not allowed to store it or modify.

The most popular architecture using this approach is a JavaScript Redux. It also takes place in the Flutter BLoC patter. And in Swift, TCA (The Composable Architecture) uses it.

Every architecture uses different implementation of Model layer. Let’s take a look at the Redux implementation. Let’s take a closer look at the Redux implementation, which consists of four main entities: State, Event, Reducer, and Store.

  • State — is a state of a current module/screen. This is the object that Model updates and sends to the View;
  • Event (Action) — it is being sent to the Model layer by View, and triggers State update;
  • Reducer — it’s a function that takes current State and Event as arguments and returns new State. Reducer is the only place where State can be changed;
  • Store — it’s an entity that keeps all elements together and perform communication between them. It takes Events and with help of the Reducer maps them into new States.

As you can see, there are some particular architectures that are built around UDF approach. However, it doesn’t mean that you cannot use it with other architectures such as MVP, VIPER, MVVM or any other MV- architecture. On my current project, which uses the VIPER architecture, we were able to implement the UDF approach, and it really helped us.

Two main benefits of the UDF are:

  • Predictable state management;
  • Separation of the state management and UI.

Predictable state management means that UDF provides a clear and predictable way to manage state of the module/screen/app. With a single source of truth (data source) you can be sure, that UI receives updates only from one source. It makes debugging, testing and maintenance a way easier. It also reduces the number of possible bugs and issues.

UDF separates user interface and state management into two separate parts: Model and UI. With this separation, you can write better tests for the Model layer, and it also gives you a lot more flexibility with the UI. As of 2023, many apps are still using UIKit for the UI. However, the day when we will move to the SwiftUI is coming. With UDF, it is much easier to migrate to SwiftUI since state management and UI are separated, and all you need to change is the UI.

How to integrate into an existing project

If you are using VIPER, MVP or any other architecture you can integrate UDF approach into your codebase. There is no need to change an entire architecture. All you need to is to set up State/Event communication between View and Logic (Presenter, Interactor, Intent) layers, and set up Reducer logic inside your Logic layer. You may do it using reactive libraries, in case you are already using them. After these changes, you won’t have a fully UDF-following pattern like TCA or Redux, but you will have this single data flow between your layers, and it will solve many issues.

The question is how to find time to introduce these changes. We all have tasks to complete, and product teams want us to deliver new features as soon as possible and fix all the bugs even faster. However, the good news is that you don’t have to rewrite the entire codebase to implement these changes.

What you can do is start creating new modules using UDF approach. This way, you will stop creating any new pieces of code using your previous architecture. But what to do with an existing code? You can work on refactoring of old modules at the moment when you are fixing or adding something to those modules. It will take time, but after some time you will have most of the code updated. And it won’t interrupt your development process. Maybe just a little bit.

I hope you found this article useful, and maybe you will try to use UDF in your new or current projects. I’d be happy to see your opinion about this topic in the comments. Thanks for your time!

Check out my other articles about iOS Development

https://medium.com/@artem.khalilyaev

--

--