New iOS Software Architecture: 4V Engine
Should you read this article?
This article is about a new software architecture which has more layers than VIPER and MVVM-C. It means that it may be more complex than other known architectures.
If you want to continue reading this article, you must accept my point of view that if we want a clean and testable architecture we should have several layers with just a responsibility.
I don’t want to sell it as the perfect architecture which can solve all your problems. It may not suit your needs perfectly. For this reason, I would suggest you to read this article thoroughly and judge by yourself if this architecture makes sense for your projects.
If you are wondering why MVC is not enough for iOS development, I would suggest you to jump in Why MVC is not enough.
I created this blog writing an article about MVVM-C. Then, I wrote an article about SOLID principles. At this point, you may be thinking that I don’t practice what I preach. If I speak about “Single Responsibility Principle”, why do I want to use MVVM-C even if the
Coordinator layer has more than one responsibility? It creates the stack (
View in a parent
UIKit component and decides the routing adding/removing child coordinators. MVVM-C must be refactored a little bit to avoid breaking “Single Responsibility Principle”.
In this article, I will explain an alternative to the main iOS software architectures: 4V Engine.
Why MVC is not enough
I’ve already covered this point in a my previous article about MVVM-C but I want to write it again because I think it’s very important.
A common approach to become an iOS developer is looking at the documentation and following the patterns suggested by Apple to write some plain projects. It means that the majority of us have begun using MVC as software architecture to create our first applications. Step by step, we started to get used to MVC. At this point of the learning process, we think that MVC is the right way. It works, why should we move to another architecture which adds complexity to our code?
There are, mainly, two reasons:
- SOLID principles: The view controller has too many responsibilities.
- Testability: The view controller is difficult to test since it’s tightly coupled with
If we want to solve the points above, we should move to another architecture. Unfortunately, nothing comes for free. This change has a cost: complexity. If we look at VIPER and MVVM-C, we can notice that there are several layers to manage and let communicate together. It may be overkilling if we have a plain application or we don’t really care of SOLID and testability.
My personal point of view is:
I like having a well-written code which follows the SOLID principles and properly tested to avoid bugs as much as possible. For this reason, I wanted to spend time and efforts to create a new clean software architecture.
Problems of MVVM-C
As said previously, MVVM-C is not enough. The
Coordinator layer breaks “Single Responsibility Principle” and must be split in three components with the following responsibilities:
- Manage the app routing.
- Create the stack (
Viewin a parent
This idea of splitting the
Coordinator led me to 4V Engine.
I want to thank my friend Ennio Masi for pointing out this
Coordinator problem, which motivated me to find a solution.
Problems of VIPER
Another famous iOS architecture is VIPER. It has several layers which are very similar to MVVM-C with a different naming.
If we analyze VIPER, we can find the same problem of MVVM-C. The guilty layer is called
Wireframe and it’s the router of the architecture.
Let’s use, for the sake of the explanation, the following code copied from VIPER-SWIFT:
We can notice that
presentAddInterfaceFromViewController has too many responsabilities:
- Manage the routing.
- Create the
Viewand its properties.
Viewin the parent
We can notice that—with these three points—we have the same problems of the MVVM-C
We’ve analyzed the common iOS software architectures and we have found some problems. If we are willing developers and we want to improve our code, we would need an alternative which refactors the previous ones. With this goal in mind, I created this new software architecture:
Don’t be afraid, it may be confusing but we are going through the explanation of each layer soon.
As we can see in the diagram above, the core of this architecture is made by
View Model, for this reason this architecture is called 4V Engine.
Now, it’s time to explain each single layer. Since I think that jumping in the code is the best way to learn something, we’ll use a sample app to cover each layer with an example.
You can find the Github repo here.
It’s a very plain application with two components:
- Users List: a users list fetched from a remote API.
- User Details: a view with the name of the user selected in the users list-we can select a user tapping the info button of a
UITableViewCellin the users list.
I think the best way to explain the layers is starting from bottom (
Model) to top (
Router). Let’s start.
The model represents the data of our application.
In our sample app, we have a model
which represents the single user parsed from the API response.
Interactor is the same used in VIPER.
This layer manages the
Model to prepare the data for the
View Model. The
View Model shouldn’t perform any operations directly on the model, but it should delegate
Interactor for any data manipulations.
In our sample app, we have an interactor which fetches the users from a remote API—thanks to the service
HTTPClient—and then transforms the JSON data received in an array of
User—thanks to the helper class
UsersParser—to be used inside our
We can consider
View Model the most important layer of this architecture. Its responsibility is to interact with the UI to decide what to show and how to behave after an UI action.
This layer shouldn’t have any
UIKit references. If we want a communication between
View Model, we should use an UI binding mechanism. I’ve already shown the main mechanisms in a my previous article. For this sample app, I’ve decided to avoid RxSwift for the binding since it would have increased the complexity of the examples. To keep everything as plain as possible, the binding has been achieved with the delegation pattern.
We can use
View Model with an its
Interactor to get the data to show in the UI, as we can see in the sample app:
With MVC, we are used to keep the business logic inside the view controller. With 4V Engine, we can move the business logic inside the
View Model and test it easily since we don’t have dependencies with
navigationDelegateis used to communicate with
Router. We’ll see it in Router.
- The method
bindis used for the UI binding between
View Model. We’ll see it in View.
View layer represents any
UIKit components used to show something in the device screen.
In the sample app, the
View is a
UIViewController for the user details and a
UITableViewController for the users list.
The advantage of a good architecture is that we can test easily our layers.
View is usually the most difficult to test because it’s coupled with its dependency
UIKit. For this reason, we must keep this layer as plain as possible and move the business logic in a testable layer. The “testable” layer is the
View Model. As we’ve seen in View Model, the UI data is driven by the
View Model. In this way, we can move the business logic inside
View becomes a dumb layer, which is used merely to show something in the device screen.
The important concept to understand with the
View is the UI binding, which allows us to set the communication between
View Model and
View. If you don’t know what is the UI Binding, please have a look at my previous article.
Here an example of
View from the sample app:
As we can see in this example, the UI binding is often bidirectional, some times we ask some data to
View Model and some times we get notified by
View Model to update the UI—like with
viewModel has the keyword
unowned. It’s required to avoid a retain cycle. Since
View Factory already keeps a strong reference of
View Model—as we’ll see in View Factory—
View doesn’t need to keep a strong reference of its
So far, the layers have been very similar to VIPER and MVVM-C. Now, it’s time to explain the layers which may be confusing at first glance.
The responsibility of
View Factory is creating the core of the architecture:
View Model and
View Factory alone may not make a lot of sense, we must see it in the right context. We’ll understand its usage in View Presenter.
Let’s see an example from the sample app:
- We are exposing
viewControllerto be used in
View Presenter. We may also expose the
View Modelfor specific reasons. I think it can be private most of the time. The decision depends on what you have to achieve.
- We are injecting
UsersListViewModelto let the
Routercommunicate with the
View Modelin an abstract way. We’ll see the details of this delegate in Router.
The name of this layer may be a little bit confusing. We’re used to call
Presenter the layer which updates the
View—we have this layer in VIPER and MVP. In this architecture, the presenter is called
View Model and this layer is not a presenter but a View presenter. Keep reading to understand its responsibility.
View Presenter is the last piece of the puzzle of a component written with 4V Engine.
This layer has the responsibility to show the component in the device screen.
To achieve this goal, it must know what and where to show. The
View to add is provided by the
View Factory and the parent is injected from outside.
Let’s see an example from the sample app:
In this example,
present() is a very plain method to add a child view controller. If you have fancy
UIViewController transitions, this method is the right place to manage them.
You can notice that we are propagating
UsersListNavigationDelegate through the layers to use it in the
View Model. This is the downside of splitting the architecture in several layers.
We’ve just finished to see the layers of a single component. At this point, we have a component almost ready to be shown in the screen. We need a last step: to decide when to show the component. This is the responsibility of
We usually have a
Router per story. In this context, my definition of story is:
The set of components which, together, define a flow in our application.
In our sample app, we have the story
Users which is the set of users list and user details together. Other stories can be:
- Onboarding: set of views to show how to use the application.
- Items Purchase: set of views to show the basket, add delivery address, add card details for the payment, …
Let’s see how to use
Router for the story
Users in the sample app:
Routerhas a dictionary with the presenters used. In this way, if we want to remove a component like in
userDetailsCloseDidTap, we can easily get the right presenter using a key.
We can have a look at
AppDelegate to understand how to use the router:
I consider this article a presentation of the version
1.0.0 of 4V Engine. I changed it a lot of time and I’m sure that there is still room of improvement. For this reason, I would like some comments with your opinions, it would be greatly appreciated. Thank you.