DEV Community

Eleazar Estrella
Eleazar Estrella

Posted on • Updated on

MVVM Pattern Sample in Swift/iOS

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.

https://thepracticaldev.s3.amazonaws.com/i/ibcak32czzov3pwvyskp.png

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 }
}
Enter fullscreen mode Exit fullscreen mode

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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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()
    }
}

...
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (3)

Collapse
egabor profile image
egabor

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!

Collapse
eleazar0425 profile image
Eleazar Estrella Author

I'll give it a try!

Collapse
hlc0000 profile image
Comment marked as low quality/non-constructive by the community. View Code of Conduct
hlc0000

Hello, I'm an IOS developer. Recently, when using swift to develop applications, I found that there are few caches written by pure swift. So I wrote a cache -- swiftlycache, a lightweight general-purpose IOS cache library using swift 5. If you are using swift for development, if you also need to use cache, maybe you can try swiftlycache, maybe you will like it, If you like, you can also introduce it to your friends. Thank you
github.com/hlc0000/SwiftlyCache