Xcoding with Alfian

Software Development Videos & Tutorials

Simple iOS MVVM With Cocoa Key Value Observer

Alt text

Building iOS application using MVVM architecture has many advantages compared to using MVC. One of advantage is reducing Massive View Controller issues by separating the Model and View to a separate MVVM object, View Controller responsibilities are only observing the value related to the view in the ViewModel then update the view when the value changes.

This leads to less coupled View Controller interaction with the Model. Unit Testing become more easier because we can test the ViewModel separately from View Controller.

Current State of MVVM In an iOS App

Many iOS app that is built with MVVM architecture uses third party library such as RxSwift and RxCocoa to observe change in View Model to the View Controller. RxSwift is a very powerful library that we can use to transform observers with combination, merge, zip, and many other powerful transformations.

While RxSwift is very powerful, sometimes we just want to build simple and small app without third party dependencies using native Cocoa Foundation framework.

Using MVVM Without Third Party Dependencies

Is there any built in solution that the Apple Foundation Framework provides that we can use to build a MVVM app in iOS architecture?. Yes, the answer is Cocoa Key Value Observing. KVO is an observer pattern that can be used to observe value of property change in an object with NSObject as their superclass.

Cocoa KVO is a very powerful mechanism we can use to build MVVM based architecture in iOS. The disadvantage is the syntax is very verbose to use to and we have to keep and remove observer manually, RxSwift provides the addToDisposeBag method that can be used to remove observer automatically.

Implementing KVO to Build iOS MVVM Based App

Here is the sample iOS App that implements KVO to build a simple email validation when user types in a TextField. The Application into 3 main components:

  1. ViewController is a UITableViewController that displays UITextField to enter email and UILabel to display if the email is valid.
  2. EmailModel is a plain Swift Class that store the String of the email and the boolean value that represent if the email is valid.
  3. EmailViewModel is a Cocoa NSObject superclass that provides properties that will be displays the email text and whether it is a valid email format.

EmailModel

import Foundation

class EmailModel {
    
    static let valueDidChange = Notification.Name("valueDidChange")
    static let isValidDidChange = Notification.Name("isValidDidChange")
    static private let regexEmail = "[A-Z0-9a-z\\._%+-]+@([A-Za-z0-9-]+\\.)+[A-Za-z]{2,4}"
    
    init(value: String) {
        self.value = value
    }

    var value: String = "" {
        willSet {
            NotificationCenter.default.post(name: EmailModel.valueDidChange, object: self, userInfo: [\EmailModel.value: newValue])
            self.isValid = validateEmail(newValue)
        }
    }
    
    public private(set) var isValid: Bool = false {
        willSet {
            NotificationCenter.default.post(name: EmailModel.isValidDidChange, object: self, userInfo: [\EmailModel.isValid: newValue])
        }
    }
    
    private func validateEmail(_ email: String) -> Bool {
        guard !email.isEmpty else { return false }
        let emailPredicate = NSPredicate(format: "SELF MATCHES %@",  EmailModel.regexEmail)
        let emailEvaluate = emailPredicate.evaluate(with: email)
        guard emailEvaluate else { return false }
        return true
    }
    
}

EmailModel store the value text of the email and isValid boolean that represent if the email is valid. Swift property observer is used when the value changes, a Notification with the EmailModel.TextDidChange is posted using the NotificationCenter containing the userInfo with the new value, it also set the value of isValid by calling the validateEmail passing the newValue of the email text to be evaluated using RegEx expression. The change in isValid boolean is also posted using NotificationCenter.

EmailViewModel

import Foundation

class EmailViewModel: NSObject {
    
    private let emailModel: EmailModel
    @objc dynamic var emailTextValue: String
    @objc dynamic var isValidValue: Bool
    
    private var emailTextObserver: NSObjectProtocol?
    private var isValidObserver: NSObjectProtocol?
    
    init(emailModel: EmailModel) {
        self.emailModel = emailModel
        self.emailTextValue = emailModel.value
        self.isValidValue = emailModel.isValid
        super.init()
        self.emailTextObserver = NotificationCenter.default.addObserver(forName: EmailModel.valueDidChange, object: nil, queue: nil, using: { [weak self] (note) in
            self?.emailTextValue = note.userInfo?[\EmailModel.value] as? String ?? ""
        })
        
        self.isValidObserver = NotificationCenter.default.addObserver(forName: EmailModel.isValidDidChange, object: nil, queue: nil, using: { [weak self] (note) in
            self?.isValidValue = note.userInfo?[\EmailModel.isValid] as? Bool ?? false
        })
    }
    
    public func updateEmailTextValue(_ value: String) {
        self.emailModel.value = value
    }
    
    deinit {
        if let emailTextObserver = emailTextObserver {
            NotificationCenter.default.removeObserver(emailTextObserver, name: EmailModel.valueDidChange, object: nil)
        }
        
        if let isValidObserver = isValidObserver {
            NotificationCenter.default.removeObserver(isValidObserver, name: EmailModel.isValidDidChange, object: nil)
        }
    }
    
}

EmailViewModel is an MVVM based on NSObject superclass that accept the EmailModel as its constructor. It provides emailTextValue and isValidValue that is using the objc dynamic keyword that can be used to exposes KVO to the property when the class is declared using Swift.

NotificationCenter is used to observe when the EmailModel text value and isValid value is changed then it updates the emailTextValue and isValid value with the new value inside the userInfo dictionary. This ViewModel can be used by any view controller that want to display an email value and its format validity to update its view. The EmailViewModel also exposes public method that can be invoked with the new email text value to assign to EmailModel.

ViewController Observer to EmailViewModel

import UIKit

class ViewController: UITableViewController {
    
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var emailValidationLabel: UILabel!
    let emailViewModel = EmailViewModel(emailModel: EmailModel(value: ""))
    var emailTextObserver: NSObjectProtocol?
    var isValidEmailObserver: NSObjectProtocol?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        observeViewModel()
    }
    
    func observeViewModel() {
        self.emailTextObserver = emailViewModel.observe(\EmailViewModel.emailTextValue, options: [.initial, .new], changeHandler: { [weak self] (_, change) in
            self?.emailTextField.text = change.newValue
        })
        
        self.isValidEmailObserver = emailViewModel.observe(\EmailViewModel.isValidValue, options: [.initial, .new], changeHandler: { [weak self] (_, change) in
            guard let strongSelf = self else { return }
            let isValid = change.newValue ?? false
            self?.emailValidationLabel.text = isValid ? "Email is valid" : "Email is invalid"
            self?.emailValidationLabel.textColor = isValid ? strongSelf.view.tintColor : .red
        })
        
        self.emailTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
    }
    
    @objc func textFieldDidChange(_ sender: UITextField) {
        self.emailViewModel.updateEmailTextValue(self.emailTextField.text ?? "")
    }
    
}

The ViewController class assign the EmailViewModel as its property for dependency injection, when using storyboard we can inject this property from previous view controller when the prepareForSegue is called.

Inside the observeViewModel method the ViewController observe the emailTextValue and isValid Keypath from the EmailViewModel. When new value from the EmailModel is updated the ViewController will receive the new value and then assign the value to the TextField. The isValid changes is used to determine whether the email is valid or not then display the message using UILabel.

We don’t have RXCocoa to observe the changes in UITextField, so we use the old Cocoa Target Action Selector to invoke the function when the TextField editing value event changes as user types. Inside the method we call the EmailViewModel update emailTextValue passing the new value of the TextField. This provides bidirectional binding between the View and the ViewModel.

Conclusion

Building iOS App with the MVVM Architecture is so much maintainable and scalable compared to the tightly coupled MVC architecture. We can use simple Cocoa KVO to bind our Model, ViewModel, and ViewController without using Third Party Framework such as RxSwift. For a simple project its very good, but the powerful RxSwift transform capabilities provides much more flexibility when we are building complex app with many states combination. You can see the full sample code in the project Github Repository. Also i want to mention one of the best iOS book, objc.io App Architecture book that provides an inspiration for me to write this article.