MVVM in iOS

MVVM in iOS





Related:
Lately there has been a lot of talk about MVVM in iOS applications and how it can help to tame the symptoms of massive view controllers. The default architecture pattern for building iOS applications is MVC (Model View Controller) and although there is nothing wrong with MVC pattern, but most of the time view controller becomes the point of code dumping ground.
MVVM is not something new that just popped out of nowhere, it has been used for several years specially in the .NET community. I have worked on many large scale WPF (Windows Presentation Foundation) applications where MVVM was used successfully.
Having said that comparing .NET WPF framework to iOS framework is not fair because WPF uses XAML and the power of “Dependency Properties” to perform two way seamless binding. In iOS we don’t have anything similar to dependency properties and even if we had we don’t have declarative UI code like XAML.
The idea behind MVVM pattern is that each View on the screen will be backed by a View Model that represents the data for the view. This means if we are building a News application then the view might consists of a UITableView which allows to display the news items and the view model will consists of the data representing the news item like title, description, publishedDate, author, source etc.
A sample view is shown below. It consists of a simple UITableView control, which uses the default UITableView cell for display.
Now, think about what data you want to display in the view. For sake of simplicity we will display title and description. So, view model will consists of properties that can provide the title and the description.
Our first implementation might look something like this:

struct ArticleViewModel {
    
    var title :String
    var description :String
}

extension ArticleViewModel {
    
    init(article :Article) {
        self.title = article.title
        self.description = article.description
    }
}

This certainly represents the data that we want to display in the UITableView but it does have few short comings. It only represents the part of the view that can be populated. The view model should be able to accommodate the complete view. Check out the implementation below:

struct ArticleListViewModel {
    
    var title :String? = "Articles"
    var articles :[ArticleViewModel] = [ArticleViewModel]()
}

extension ArticleListViewModel {
    
    init(articles :[ArticleViewModel]) {
        self.articles = articles
    }
    
}

By adding a parent view model “ArticleListViewModel” we have created more flexibility to control the items on the screen. If in the future we need to add a search bar then we can simply add a reference to the SearchViewModel inside our ArticleListViewModel.

View Models + Networking = ?

While researching on the MVVM implementations in iOS I came across a lot of resources where the developer implemented the networking code and data access code right inside the view model. One of the possible implementations is shown below:


struct ArticleListViewModel {
    
    var title :String? = "Articles"
    var articles :[ArticleViewModel] = [ArticleViewModel]()
}

extension ArticleListViewModel {
    
    init(articles :[ArticleViewModel]) {
        self.articles = articles
    }
    
    func loadArticles(callback :(([Article]) -> ())) {
        
        URLSession.shared.dataTask(with: url) { data, response, error in
            
            if let data = data {
                
                let json = try! JSONSerialization.jsonObject(with: data, options: [])
                let dictionary = json as! JSONDictionary
                
                let articleDictionaries = dictionary["articles"] as! [JSONDictionary]
                
                articles = articleDictionaries.flatMap { dictionary in
                    return Article(dictionary :dictionary)
                }
            }
            
            DispatchQueue.main.async {
                callback(articles)
            }
    } 
}


Your view model should be as dumb as possible. It should not contain networking code or data access code. When you start performing networking operations inside view model you are adding tight coupling between the view model layer and the web services layer.
UPDATE: As few readers pointed out that It is not fair for me to say that your view model should not invoke web services as Microsoft, who has been using MVVM architecture for a long time does seem to advocate the idea of calling web services from right inside the view model.
Even in the iOS community I have seen developers using different flavors of MVVM architecture. I would encourage you to choose the MVVM architecture that best fits your needs.
Networking and data access operations should be isolated from the view models and should be part of separate classes or even separate framework/library. Depending on your application, networking and data access operations can be passed into the view controller constructor as dependency injections.
Here is an implementation of a web service call that happens inside the view controller but controlled by a separate web service class.


    private func loadArticles() {
        
        // this url should be part of the URL builder scheme and not right inside the
        // view controller but right now we are focused on MVVM
        let url = URL(string: "https://newsapi.org/v1/articles?source=the-next-web&sortBy=latest&apiKey=0cf790498275413a9247f8b94b3843fd")!
        
        // this web service should use generic types. Again this is not part of the implementation
        // as we are focusing on MVVM model
        Webservice().getArticles(url: url) { articles in
            print(articles)
            
            let articles = articles.map { article in
                return ArticleViewModel(article :article)
            }
            
            self.viewModel = ArticleListViewModel(articles :articles)
            
        } 
}

The web service returns the domain objects which is then mapped to the view model objects. This mapping can be performed manually or using any iOS auto mapping tool similar to AutoMapper in .NET.
Finally, when the mapping is done we set our view model which triggers the UITableView reload. This trigger happens automatically because of the didSet property behavior. Keep in mind that didSet will not get fired when the properties are set inside the constructor of the class.


 private var viewModel :ArticleListViewModel = ArticleListViewModel()  {
        
        didSet {
            self.tableView.reloadData()
        } 
}

MVVM Bindings

Binding refers to the flow of information between the views and the view models. Consider a scenario where you have to create a registration screen. As you type the information in the text field it is also being populated on a RegistrationViewModel object … instantly. In other words you don’t have to type the following code:


self.viewModel.title = self.titleTextField.text! 
self.viewModel.description = self.descriptionTextField.text!

Binding goes both ways! This means if you change your view model properties then it is reflected on the view. This is also known as two-way binding. As I mentioned earlier that in WPF framework, binding is handled by dependency properties. Unfortunately, Swift does not have any dependency properties so we will have to do some work to get this working.
We cannot put an observer on a String type, this means we will create our custom class and allow it to be observed.


import Foundation

class Dynamic<T> {
    
    var bind :(T) -> () = { _ in }
    
    var value :T? {
        didSet {
            bind(value!)
        }
    }
    
    init(_ v :T) {
        value = v
    }
     
}

Dynamic<T> is a custom class that can hold a value of type T. If the value changes then we fire the didSet, which calls the bind function passing the value back to the caller. The implementation is shown below:


class AddArticleViewController : UIViewController {
    
    // For this example assume they are UITextField
    @IBOutlet weak var titleTextField :BindingTextField!
    @IBOutlet weak var descriptionTextField :BindingTextField!
    
    var viewModel :AddArticleViewModel! {
       
        didSet {
            viewModel.title.bind = { [unowned self] in self.titleTextField.text = $0 }
            viewModel.description.bind = { [unowned self] in self.descriptionTextField.text = $0 }
        }
    }
    
    @IBAction func AddArticleButtonPressed(_ sender: Any) {
        
        self.viewModel.title.value = "hello world"
        self.viewModel.description.value = "description"
    }
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        self.viewModel = AddArticleViewModel()
    }
     
}

This will setup one-directional binding from view model to the user interface elements. This means if the properties of view model changes then the user interface elements are notified.
In order to create a bi-directional binding you will need to implement a custom UITextField control. This is implemented below:


import Foundation
import UIKit

class BindingTextField : UITextField {
    
    var textChanged :(String) -> () = { _ in }
    
    func bind(callback :@escaping (String) -> ()) {
        
        self.textChanged = callback
        self.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
    }
    
    @objc func textFieldDidChange(_ textField :UITextField) {
        
        self.textChanged(textField.text!)
    }
     
}

We attached the editingChanged event to the UITextField and then trigger a custom callback function. Now, you can update our IBOutlets to make use of this new bind function as shown below:


  @IBOutlet weak var titleTextField :BindingTextField! {
        didSet {
            titleTextField.bind { self.viewModel.title.value = $0 }
        }
    }
    @IBOutlet weak var descriptionTextField :BindingTextField! {
        didSet {
            descriptionTextField.bind { self.viewModel.description.value = $0 }
        } 
}

Not pretty but it works!
This is just a very introductory look at the MVVM model. I believe that MVVM approach in iOS can greatly improve if Swift allows the capability of making custom atrributes. This will allow us to create dynamic validation frameworks where each view model can easily validate and return broken rules associated with the view.
As Michael Long pointed out in the comments, MVVM makes it easy to test the logic behind the views.
Another benefit of moving your ViewController’s business logic into your ViewModel is that the viewModel it then becomes a lot easier to create unit tests for those components of your application.
You can download the code here:
  • iOS
  • iOS App Development
  • Swift Programming
  • Mvvm
  • Software Development
  •  
     
     
     
     
     


    Comments

    1. IT's very informative blog and useful article thank you for sharing with us , keep posting
      Android development Course
      Swift course

      ReplyDelete
    2. Excellent blog post. Thanks for sharing this nice article.

      Are you looking for iPhone App Development Company in UK? Reach Way2Smile Solutions UK.

      ReplyDelete
    3. Astounding post! I took in a ton about spry strategies for Mobile App Development Company

      ReplyDelete
    4. Amazing one!. Interesting blog post. It was really a nice topic. Thanks for sharing this wonderful Article.

      Mobile App Development Chennai

      ReplyDelete
    5. If you’re the one looking for the best Mobile app development solutions you can totally rely on AppClues Infotech – A foremost iOS mobile app development company in the USA. They have helped diverse businesses to build top mobile apps tailored to your specific requirements.

      iOS App Development Company
      Android App Development Company
      Cross-Platform App Development Company
      Enterprise App Development Company
      MCommerce Mobile App Development Company
      Wallet App Development Company

      ReplyDelete
    6. App On Radar is committed to delivering top-notch iOS development services, leveraging cutting-edge technologies and industry best practices. Our team of experts ensures seamless integration, robust functionality, and stunning design to create standout iOS applications that resonate with users.

      ReplyDelete
    7. Embracing MVVM in iOS applications is gaining traction, offering a solution to mitigate the issue of bloated view controllers. Penguin Book Writers can leverage this alternative architecture pattern to enhance code organization and maintainability in their iOS projects.

      ReplyDelete

    Post a Comment

    Popular posts from this blog

    iOS Architecture

    Property vs Instance Variable (iVar) in Objective-c [Small Concept but Great Understanding..]

    setNeedsLayout vs layoutIfNeeded Explained