MVVM is one of the most popular topics among iOS community. It has drawn a lot of attentions. Surely we can write code which is reusable and easy to maintain in MVC. And MVVM is not a silver bullet. As Rui Peres said in his MVVM presentation :
Architectural patterns come and go, but good engineering principles stay.
In this article, I am not going to explain what is MVVM. We will look at MVVM from the data flow prospective. It can help us to understand each role, how to construct our MVVM code, and why it should be written in this way.
Before we get started, first you need to know that View in MVVM contains views and view controllers. There are not only Model, View, and View Model, but also Controller. The most important design principle I learned is Single Responsibility Principle, SRP for short. Without Controller, I found it almost impossible to write code that respects SRP.
Most apps we make need to get data from data storage. Data storage can be a server, a local database, a persistent file, or just in-memory storage, etc. There is a data flow. Generally speaking, a user requests some data from data storage, data storage sends back the data, and then UI is updated to inform the changes. We care about the data flow because it helps us to separate our code into different layers. By doing this, we could clearly see each object's responsibilities.
Presentation layer is what a user sees and interacts with. In this layer, we have views, view controllers, and view models.
View controller's responsibilities: - Handle user interactions - Bind view and view model
View's responsibilities: - Subview allocation - Subview layout
View model's responsibilities: - Model transformation
View model transforms model to what users perceive. Models are received from domain layer (or data layer, if models does not requires additional process in domain layer).
As for data fetching, it should be encapsulated in its own class. It does not belong to presentation layer. Where it belongs depends on which layer the models it fetched from. For example, if we can get models from data layer, it belongs to data layer.
In MVVM, data fetching can be invoked in either view controllers or view models. Personally, I prefer view controllers, and a view controller also bind the fetched models to its view model.
There might be controllers in this layer, such as a coordinator object that handles navigation flow. You can read more about this topic from two great articles by Soroush Khanlou :
Note that views and view controllers should be main-thread-only. When you have a task that can block the main thread, and you think of using GCD or NSOperation to run it on a background queue. Then this task should be isolated in another object, hiding the concurrent details from the rest of the app.
Domain layer handles business logic. If your apps only need to fetch data from a server, and then present to users, you may not need domain layer. View models in presentation layer can transform models from data layer directly.
In our app, we have a feature called daily tasks. First we fetch today's tasks from server. When a user plays a song, views some page or joins a group, we check whether the user's action meets the criteria of today's tasks. If so, the user completes the task, and gets rewards. The criteria checking logic is encapsulated in a controller object that belongs to domain layer.
Data layer primarily contains data model and data access.
Data model is the Model we know in MVC and MVVM. They are value objects. The responsibility of a model is to transform and store the data.
What is data access? If our data is from server, data access is the class making API request. If our data from database, data access is the class that manages read and write access to the database. Data access are all controllers. They manage the data access in our app.
Where should we go from here?
We can also apply Flux/Redux to the data flow, making it unidirectional. States are restricted in data layer. The less state we need to manage, the better.