Scalable Modular Architecture in iOS
In this article I am gonna talk about how we can prepare design architecture for large iOS application.
Designing an architecture for large applications is not an easy task. The purpose of the architecture is to make the system easy to develop, easy to maintain, accelerate further feature development, and minimize any blockers.
In this article I will not talk about UI architectures like MVVM, MVP, VIPER, VIP, or any other architecture. When we talk about large-scale applications, the architecture we mean is how each software component communicates with each other and how to manage dependencies between software components. We also have to think about how to create an architecture that allows engineers to perform feature development and testing in isolation.
Let’s try to see the architectural design below:
For small-scale applications, the architectural design above is the most reasonable option. Of course, we don’t want to make an over-engineering technical decision. But if we have a large-scale application, are there any problems resulting from the above architecture?
The above architecture does not have a presentation layer, which is responsible for formatting the data so that it is ready to be consumed by the UI layer. With the above architecture, the task of formatting the data will likely be done inside the view controller or in the Movie domain. This is what causes the Massive View Controller in the future, which makes our code untestable and hinders further feature development.
The architecture above is better than before because we create a presentation layer in the form of a view model. This view model is responsible for preparing data that is ready to be consumed for the view. However, there are still some problems with the design architecture above. The presentation layer has direct access to the NetworkService module and the Movie module which causes the view model to be tightly coupled with the network module and the domain module. This causes the presentation layer cannot be reused because it has dependencies. There are several ways to solve this problem, one of which is to create an adapter layer and composing the object in the composition root, which in this case is in the Main App module.
With the above architecture, the feature module no longer has a dependency on the domain module or the service module. Likewise with the domain module and also the service module. We can reuse the module to speed up further feature development. In addition, to make communication between feature modules to service modules or domain modules, it is done by creating an adapter layer created in the Main App module.
Sample Project
To implement the architectural design above, I have created a sample project with a modular architecture using the Swift package manager.
You can clone to the repository above, and don’t forget to register at https://www.themoviedb.org/ to get the API key.
Here is an overview of the application I made:
The demo application that I made is quite simple and has two features, including a page to display a list of popular movies, then when the user tap a movie it will move to the Movie detail page.
Adapter Layer Implementation
The adapter layer here is made to compose objects in the main app module. Here is the code snippet for the MovieListViewController:
As you can see, MovieListViewController has a delegate property which is responsible for getting the data either MovieViewModel or ErrorViewModel. Here it does not care about the data entities in the domain module, the MovieListViewController only needs a presentation type that can provide ready-consumable data. Here is the adapter implementation for MovieListViewController:
MovieListAdapterDelegate is responsible for doing network requests to get the Movie entity, then it will create a movie view model which will be returned to MovieListViewController using closure.
In addition to MovieListAdapterDelegate, we also need to create an extension of MovieViewModel in the Main App module, so that the MovieViewModel in the MovieFeature module does not have a dependency on the Domain module.
Then how to set the navigation from MovieListViewController to MovieDetailViewController without making each other interdependent?
We can encapsulate the action into a MovieViewModel. As in the code snippet above, there is one property in the form of a closure, namely selection.
We simply invoke selection on the movie view model, this is useful so that MovieListViewController does not have the responsibility to handle the navigation logic that makes MovieListViewController and MovieDetailViewController independent of each other.
Conclusion
To see the overall implementation, please clone the repository that I have listed above. In building an architecture, don’t think of a solution directly like MVVM, VIP, or any other architecture. We need to understand that architecture is made based on existing problems and needs. The architectural design above that I made cannot be implemented directly in other projects that have different problems and goals. Therefore it is very important to focus on the problem and the goals that want to be achieved.
Thank you for reading this article, don’t forget to clap this article if it is useful for you. Don’t forget to also star in my repository, if you have any feedbacks or questions, please write them in the comments section or you can ask me directly via my linkedin. Thanks again and see you in the next article!