MVVM is a pattern that has been gaining more popularity, while more event-oriented applications have been becoming. There are many advantages to using MVVM vs. the classic MVC in iOS development, starting with completely dividing the business logic with the presentation layer. Instead of having a "massive ViewController" that is responsible for doing too many things, we can delegate things like requesting data from the network or a local database to another entity.
All this application logic is within the ViewModel, which never knows what the view is or what the view does. What makes this architecture quite testable and takes complexity away from the view leaving it as dumb as possible.
The view owns the ViewModel and is always "listening" for changes for updating the UI. Here comes the famous "two-way data binding," if there is a change in the view, the model is updated, and if there is a change in the model, the view is updated, always having as an intermediary the ViewModel.
Well, what if we see this in practice? Imagine the following scenario: I want to develop an app that connects with the API rest of Github and allows me to search among the repositories, select one of them, and see its most recent commits. Let's get started!
I'm going to start with the search functionality, I will ignore many things for brevity, but you can see all the source code at the end.
Let's code the protocol that satisfies our ViewModel:
protocol SearchRepositoriesDelegate {
func searchResultsDidChanged()
}
protocol SearchViewModelType {
var results: [SearchResult] {get}
var query: String {get set}
var delegate: SearchRepositoriesDelegate? {get set }
}
A property for results, a property for the query that retrieves those results, and a delegate to notify the view that there have been changes. Simple, right?
Please note that 'SearchRepositoriesDelegate' in another scenario should be replaced by some data binding mechanism or implement observables, but this will be for another post.
Then our ViewModel will work in the following way:
1- The view will have a reference to the ViewModel.
2- While the user is typing in the search bar, the view will update the 'query' property of the ViewModel based on the query.
3- Each time the 'query' property is updated, the ViewModel makes a request to the Github API rest and updates the results.
4- Once there is a server response, the ViewModel notifies the view (through the delegate) that there were changes in the results.
5- Every time the function 'searchResultsDidChanged' is called, the view updates the UI.
Let's see how the implementation is:
ViewModel:
class SearchViewModel : SearchViewModelType {
var delegate: SearchRepositoriesDelegate?
var results : [SearchResult] = [] {//0 results by default
didSet{
delegate?.searchResultsDidChanged() //notify
}
}
var searchService: SearchService
var query: String = "" {
didSet {
if query == "" {
results = []
}else {
performSearch()
}
}
}
init(service: SearchService) {
self.searchService = service
}
private func performSearch() {
searchService.search(query: self.query)
.onSuccess { results in
self.results = results
}.onFailure { error in
//do nothing
}
}
}
View:
class SearchViewController: UIViewController {
...
var searchViewModel: SearchViewModelType!
override func viewDidLoad() {
super.viewDidLoad()
searchViewModel.delegate = self
...
}
}
extension SearchViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchViewModel.query = searchText
}
}
extension SearchViewController: SearchRepositoriesDelegate {
func searchResultsDidChanged() {
self.tableView.reloadData()
}
}
...
While the user is typing in the search bar, the property 'query' is updated, and the ViewModel requests the server-side. Once a request completes, it notifies the view, and it calls the UITableView 'reloadData ()' function to reflect the changes in the UI. As you may have seen, the class SearchViewModel is very easy to test since we only need to create a mock 'SearchService' object to check if it works correctly. ViewModel has no reference to the view, and the view is exempt from all business logic.
That's all! Don't be afraid to see all the source code in this link and comment on any questions.
Top comments (3)
You should also check out the tool that I’ve made for iOS making the MVVM development faster: github.com/egabor/mvcvm-swift-file...
It’s combined with RxSwift!
I'll give it a try!
Some comments may only be visible to logged-in visitors. Sign in to view all comments.