This tutorial takes a deep look at architectural patterns, analyzing their benefits to help you create a robust, easy-to-test mobile iOS application with Swift.
Nowadays, the biggest challenge for an iOS developer is to craft a robust application which must be easy to maintain, test, and scale. In this article, you will learn a reliable approach to achieve it.
First of all, you need a brief introduction of what you’re going to learn: architectural patterns.
To quote Wikipedia, “An architectural pattern is a general, reusable solution to a commonly occurring problem in software architecture within a given context. Architectural patterns are similar to software design pattern but have a broader scope. The architectural patterns address various issues in software engineering, such as computer hardware performance limitations, high availability and minimization of a business risk. Some architectural patterns have been implemented within software frameworks.”
When you start a new project or feature, you should spend some time thinking about the architectural pattern to use. With a good analysis, you may avoid spending days on refactoring because of a messy codebase.
There are several architectural patterns available and you can use more than one in your project, since each one can better suit a specific scenario.
When you read about these kinds of patterns, you mainly come across these:
Model-view-controller is the most common, and you might have used it since your first iOS project. Unfortunately, it’s also the worst, because the Controller has to manage every dependency (API, Database, and so on) , contains the business logic of your application, and is tightly coupled with UIKit — this means that it’s very difficult to test.
You should usually avoid this pattern and replace it with the next ones.
Model-view-presenter is one of the first alternatives to MVC, and is a good attempt to decouple the Controller and the View .
With MVP, you have a new layer called Presenter which contains the business logic. The View —your UIViewController and any UIKit components—is a dumb object, which is updated by the Presenter and has the responsibility to notify the Presenter when an UI event is fired. Since the Presenter doesn’t have any UIKit references, it is very easy to test.
Viper is the representation of the Clean Architecture of Uncle Bob .
The power of this pattern is the proper distribution of the responsibilities in different layers. This way, you have little layers- easy to test and with a single responsibility. The problem with this pattern is that it may be overkill in most scenarios, since you have a lot of layers to manage and it may be confusing and difficult to manage.
This pattern is not easy to master; you can find further details about this architectural pattern in this article.
Last but not least, MVVM is very similar to MVP since the layers are pretty much the same. You can consider MVVM an MVP version improved thanks to the UI Binding.
UI Binding is a bridge between the View and ViewModel —mono or bidirectional—and lets these two layers communicate in a completely transparent way.
Unfortunately, iOS doesn’t have a native way to achieve it, so you must use third party libraries/frameworks or make it by yourself. There are different ways to get an UI Binding with Swift:
RxSwift is the Swift version of the family ReactiveX —once you master it, you are able to switch easily to RxJava, RxJavascript, and so on.
This framework allows you to write functional reactive programming (FRP) and thanks to the internal library RxCocoa, you are able to bind View and ViewModel easily:
I will not explain how to use RxSwift thoroughly since it would be beyond the goal of this article—it would deserve an own article to be explained properly. FRP lets you learn a new way to develop and you may start either loving or hating it. If you are not used to FRP development, you have to spend several hours before getting used and understanding how to use it properly since it is a completely different concept of programming.
An alternative framework to RxSwift is ReactiveCocoa. Check this article if you want to understand the main differences.
If you want to avoid importing and learning new frameworks you could use the delegation as an alternative. Unfortunately, using this approach you lose the power of a transparent binding since you have to make the binding manually. This version of MVVM becomes very similar to MVP. The strategy of this approach is keeping a reference of the delegate—implemented by the View —inside your ViewModel. In this way the ViewModel can update the View, without having references of UIKit objects.
Here is an example:
It’s very similar to delegation, but instead of using a delegate, you use the closures. The closures are ViewModel properties and the View uses them to update the UI. You must pay attention to avoid retain cycles in the closures using [weak self] . You can read this article for more information about retain cycles because of Swift closures.
Here is an example:
When you have to choose an architectural pattern, you have the challenge to understand which one suits better your needs. Among these patterns, MVVM is one of the best choices since it’s very powerful and easy to use at the same time.
The sample project will show how the Coordinator works and how to manage the different layers.
You can download the project source here .
The examples are simplified to keep the focus on how MVVM-C works, therefore the classes on Github may be slightly different.
The sample app is a plain dashboard app which fetches the data from a public API and, once the data is ready, allows the user to find an entity by id, like in the screenshot below:
This application has different ways to add the view controller, so you’ll see how to use the Coordinator in edge cases with child view controllers.
Its responsibility is to show a new view and to inject the dependencies which the View and ViewModel need.
The Coordinator must provide a start method to create the MVVM layers and add View in the view hierarchy.
You may often have a list of Coordinator children since in your current view you may have subviews like in our example:
You can notice that the Coordinator has a parent UIViewController object—or subclasses like UINavigationController —injected in the constructor. Since the Coordinator has the responsibility to add the View in the view hierarchy, it must know in which parent add the View.
In the example above, DashboardContainerCoordinator implements the protocol Coordinator:
which allows you to take advantage of Polymorphism .
Once you create your first Coordinator you must add it as entry point of your application in the AppDelegate:
In AppDelegate we instantiate a new DashboardContainerCoordinator and thanks to the method start we push the new view in navigationController .
You can see on the Github project how to inject a UINavigationController type decoupling UIKit and the Coordinator .
The Model is a dumb representation of the data. It must be as plain as possible without business logic.
The sample project uses the open source framework ObjectMapper to transform the JSON to an object. ObjectMapper is a framework written in Swift that makes it easy for you to convert your model objects (classes and structs) to and from JSON. It’s very useful when you have a JSON response from an API and you must create your model objects parsing the JSON string.
The View is a UIKit object—like a common UIViewController.