MVVM-C with Swift
Nowadays, the biggest challenge for an iOS developer is the craft of a robust application which must be easy to maintain, test and scale. In this article you will learn a reliable approach to achieve it.
Introduction
Nowadays, the biggest challenge for an iOS developer is the craft of 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.
Architectural Patterns
What is it?
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.
Cit. Wikipedia
When you start a new project or feature, you should spend some time thinking to the architectural pattern to use. With a good analysis, you may avoid spending days on refactoring because of a messy codebase.
Main Patterns
There are several architectural patterns available and you can use more than one in your project, since each one can suit better a specific scenario.
When you read about these kind of patterns you come across mainly with:
Model-View-Controller
This 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 dependencies (API, Database and so on), contains the business logic of your application and is tightly coupled with UIKit
—it means that it’s very difficult to test.
You should usually avoid this pattern and replace it with the next ones.
Model-View-Presenter
It’s 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
It’s is the representation of Clean Architecture of Uncle Bob.
The power of this pattern is the properly distribution of the responsibilities in different layers. In this way you have little layers easy to test and with a single responsibility. The problem with this pattern is that it may be overkilling in the most of the 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.
Model-View-ViewModel
Last but not least, MVVM is very similar to MVP since the layers are pretty much the same. You can consider MVVM a MVP version improved thanks to the UI Binding.
UI Binding is a bridge between the View
and ViewModel
—mono or bidirectional—and lets communicate these two layers 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 (or ReactiveCocoa)
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.
Delegation
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 an example:
|
|
Closures
It’s very similar to the 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 about retain cycles because of Swift closures.
Here an example:
|
|
The Pick: MVVM-C
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.
Unfortunately this pattern is not perfect, the main lack of MVVM is the routing management. We have to add a new layer to get the power of MVVM and routing in the same patterns. It becomes: Model-View-ViewModel-Coordinator (MVVM-C)
The sample project will show how the Coordinator
works and how to manage the different layers.
<img class=“aligncenter” src=mvvm-c.jpg?v=1" />
Getting Started
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.
MVVM-C Layers
Coordinator
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
childs 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
.
Model
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.
View
The View
is a UIKit
object—like a common UIViewController
.
It usually has a reference of the ViewModel
—which is injected by the Coordinator
—to create the bindings.
|
|
In this example the title of the view controller is bound to the property rx_title
of the ViewModel
. In this way when the ViewModel
updates rx_title
then the title of the view controller will be automatically updated with the new value.
ViewModel
The ViewModel
is the core layer of this architectural pattern. It has the responsibility to keep the View
and Model
updated. Since the business logic is inside this class, you should use different components with single responsibilities to keep the ViewModel
as clean as possible.
|
|
In this example the ViewModel
has a data provider injected in the constructor which is used to fetch the data from a public API. Once the data provider returns the data fetched, the ViewModel
emitting a new event by rx_usersCountInfo
with the new count of users. This new event is sent to the View
thanks to the binding which observes rx_usersCountInfo
and update the UI.
You may have multiple components inside your ViewModel
like a data controller for your database (CoreData, Realm and so on), a data provider which interacts with your API and any other external dependencies.
The ViewModel
s use RxSwift so when the type of property is a RxSwift class (Driver
, Observable
and so on) there is a rx_
prefix. It’s not mandatory but it can help you to understand which properties are RxSwift objects.
Conclusions
MVVM-C has a lot of advantages and it can improve the quality of your application. You should pay attention on which approach to use for the UI Binding since RxSwift is not easy to master, furthermore the debugging and testing may sometimes be a little bit tricky if you don’t know well what are you doing. My suggestion is to start using this architectural pattern a little at a time so you can get used of the different layers and how to keep the responsibilities well isolated and easy to test.
FAQ
Does MVVM-C have some limitations?
Yes, of course. If you’re working in a complex project you may have some edge-cases where MVVM-C may be impossible to use—or some little features where this pattern is overkilling. If you start using MVVM-C it doesn’t mean that you are forced to use it everywhere, you should always use the architectural pattern which suits better your needs.
Can I use both functional and imperative programming with RxSwift?
Yes, you can. But I’d suggest keeping imperative approaches just in the legacy code and using functional programming for the new implementations, in this way you can take advantage of the power of RxSwift. If you want to use RxSwift just for your UI Binding you can easily write imperative programming and use reactive functional programming just to set the binding.
Can I use RxSwift in an enterprise project?
It depends if you are going to start your project or if you have to maintain legacy code. In a project with legacy code you may struggle to use RxSwift and you should refactor a lot of classes. I’d suggest starting a little at a time with little classes if you have the time and the resources to do it—otherwise try using other alternatives for the UI Binding.
An important thing to consider is that at the end of the day RxSwift is another dependency to add to your project and you can risk to waste time because of RxSwift breaking changes or lack of documentation for what you want to achieve in edge-cases.