Architectures comparing for SwiftUI
Not a secret that SwiftUI is becoming more and more popular in iOS development nowadays. But there is an important task you need to do before using SwiftUI in a real app. You need to choose architecture!
Note: nobody could tell to use this or that one - it’s up to you. That is why this article does not contain buttles or versus between architectures. I will give you the information, advantages, and disadvantages of most architectures in tandem with SwiftUI usage. Let’s dive in!
First things first, VIPER / RIBs / MVVM-R / VIP
They drop out for two reasons:
- Coordinator
- Presenter
Coordinator (Router -> R) — an essential part of VIPER, RIBs, and MVVM-R architectures. It aimed to solve two problems:
- Decoupling the ViewControllers from each other
- Programmatic navigation
In SwiftUI dynamic navigation is not possible by design. The view hierarchy is static, and all the possible navigations - are defined at compile time. There is no chance to make another navigation way to the hierarchy at runtime. Navigation now depends on the State instead, which is updated every time by the binding mechanism.
Note: Even though, You still can have and/or use a Router but only as a logic separator, e.g. to remove all preparations work for navigation logic away from the model body or file. Especially when you use navigation based on UIKit or from iOS 15.0 — NavigationStack.
Presenter (P) here — an entity that is like the dialogue placed in a movie just because it needs to be there but doing nothing useful. That’s because of the new design of the data flow and view-state bindings in SwiftUI, which makes the Presenter useless.
So basically, the whole pattern - falls apart, as the problems it aimed to solve no longer exist.
Before moving on
We must remember, what Bidirectional and Unidirectional architectures are.
Bidirectional architectures are better for small and medium-sized applications which divide into some layers that transmit and receive data from each other.
On large and complex screens is difficult to navigate and manage data flow, where the data came from, where it changes, etc.
Unidirectional architectures are better for medium-sized and large applications. They divide into some layers, where one layer receives data in one type, updates, and transmits data to the next layer.
On simple screens - some layers are extreme, but you have to keep them - the first point. Another one is that if you make a few updates, you will need to proxy data through all layers. All these inconveniences - are compensated on large screens with complicated logic, and working with code is simplified because of data control.
Ok, no more talks, let’s take a closer look at suitable architectures.
CLEAN architecture
CLEAN — even though it is the progenitor of VIP & VIPER architectures that don’t suit SwiftUI flow as we talk about above, it is quite liberal of layers we should introduce, because it depends on the application domain.
To accept the CLEAN architecture requirements with SwiftUI, we probably had come to something like you see above in the scheme.
- AppState works as the single source of truth and keeps the state for the whole app: user’s data, tokens, operation system state, etc.
- Interactor encapsulates the business logic for the specific View / Views.
- View is the usual SwiftUI view, which may be stateless or stateful - it depends on the code style or situation.
- Repository is an abstract gateway for data I / O. It provides access to data services (e.g., UserDefault service, etc.)
Advantages:
- Unidirectional architecture: This is very good for scaling and complex screens.
- There is a single source of truth, which is very important for the SwiftUI state drive concept.
- Simple and fast testability of the app (just connect the AppState mock).
Disadvantages:
- Frequent updates due to a single Observable source of truth - are an unnecessary burden, even for SwiftUI with its phenomenal update rate.
- High memory usage - due to that single source of truth.
- Massive Interactor.
- Difficult to test individual parts of the app.
REDUX architecture
Redux — the architecture is slightly different from the Clean above.
- Action is a layer of simple enum for needed actions.
- Reducer encapsulates the business logic.
- State is a single source of truth.
- Store is a container for Reducer and State, that only dispatches an action to Reducer, where true magic lives.
- View is the usual SwiftUI view, which may be stateless or stateful - it depends on the code style or situation.
The Reducer takes <Action, State>
and gives a NewState
that updates the View due to the binding mechanism.
Advantages:
- Unidirectional architecture: This is very good for scaling and complex screens.
- There is a single source of truth, which is very important for the SwiftUI state drive concept.
- Simple and fast testability of the app (just connect the Store mock).
- The Store can take on some data routing work, thereby offloading the Reducer.
Disadvantages:
- Frequent updates due to a single Observable source of truth - are an unnecessary burden, even for SwiftUI with its phenomenal update rate.
- High memory usage - due to that single source of truth.
- Modularity (with the app growing) more side models appear and connect to the Store layer.
- Difficult to test individual parts of the app.
MVVM architecture
MVVM — popular architecture, which has not changed due to the concept of SwiftUI, which means it does not need a detailed presentation.
- Model is a passive or active data container.
- ViewModel encapsulates the business logic for the specific View.
- View is the usual SwiftUI view, which may be stateless or stateful - it depends on the code style or situation.
Advantages:
- Simple and clear implementation.
- High modularity: since a View is now dependent on the specific ViewModel implementation, we can reuse that View in multiple places without changing a View’s code itself.
- There are no dependencies between Views.
- Updating the View occurs only as needed which is controlled by the ViewModel.
- Simple testability of individual parts of the app.
Disadvantages:
- Bidirectional architecture: This is not very good for scaling and complex screens.
- High modularity - expansion of the code.
- There is no single source of truth, which is important for the SwiftUI state drive concept.
AppState-MVVM architecture
AppState-MVVM — is a modernized MVVM architecture, in which the missing AppState layer appears. It’s kind of a combination of Clean and MVVM architectures. The concept is easy to understand and cool for scaling: on using and updating AppState the binding is dispatched directly not to the View, but to the ViewModel, which encapsulates business logic inside and in needed form and only if need to update the View. Here we have:
- AppState - the single source of truth and keeps the state for the whole app.
- Repository is an abstract gateway for data I / O. It is also stateless, doesn’t have write access to the AppState, and contains only the logic related to working with the data.
- Service provides access to data from Repository layer (e.g., a web server, a local database, etc.) and has write access to the AppState.
- ViewModel encapsulates the business logic for the specific View.
- View is the usual SwiftUI view, which needs to be stateless because of the presence of the AppState and the ViewModel layers.
Advantages:
- Benefits of Unidirectional and Bidirectional architectures.
- There is a single source of truth, which is very important for the SwiftUI state drive concept.
- High modularity: since a View is not dependent on the AppState implementation.
- There are no dependencies between Views.
- Updating the View occurs only as needed which is controlled by the ViewModel.
- Full test coverage.
Disadvantages:
- Even greater modularity - expansion of the code.
- Dependence of the ViewModel layer on the AppState implementation.
Conclusion
Now you see the benefits and negatives of each architecture, but choosing one of them depends on the app’s acceptance criteria, your amount of time for the project, and your preferences.
There are more architectures that we don’t discuss in this article, but my mission was to take a closer look at the most popular nowadays.
So, which one would you like to use?
Have fun coding :)