iOS MVVM sans programmation réactive


Exemple

Je commencerai par une brève explication de ce qui est et pourquoi utiliser le modèle de conception Model-View-ViewModel (MVVM) dans vos applications iOS. Lorsque iOS est apparu pour la première fois, Apple a suggéré d'utiliser MVC (Model-View-Controller) comme modèle de conception. Ils l'ont montré dans tous leurs exemples et tous les premiers développeurs ont été heureux de l'utiliser parce que cela séparait bien les préoccupations entre la logique métier et l'interface utilisateur. Au fur et à mesure que les applications devenaient plus grandes et plus complexes, un nouveau problème s’appelait de manière appropriée appelé MVC (Massive View Controllers). Étant donné que toute la logique métier a été ajoutée dans ViewController, avec le temps, ils devenaient généralement trop volumineux et complexes. Pour éviter le problème de MVC, un nouveau modèle de conception a été introduit dans le monde d’iOS: le modèle MVVM (Model-View-ViewModel).

Diagramme MVVM

Le diagramme ci-dessus montre à quoi ressemble MVVM. Vous disposez d'un ViewController + View standard (dans le storyboard, XIB ou Code), qui agit en tant que View MVVM (dans le texte suivant - View référencera View MVVM). Une vue fait référence à un ViewModel, où se trouve notre logique métier. Il est important de noter que ViewModel ne sait rien de la vue et n'a jamais de référence à la vue. ViewModel a une référence à un modèle.
Cela suffit avec une partie théorique du MVVM. Plus d'informations à ce sujet peuvent être lues ici .

L'un des principaux problèmes liés à MVVM est la mise à jour de View via ViewModel lorsque ViewModel ne contient aucune référence et ne connaît même pas la vue.

La partie principale de cet exemple est de montrer comment utiliser MVVM (plus précisément comment lier ViewModel et View) sans programmation réactive (ReactiveCocoa, ReactiveSwift ou RxSwif). Juste comme une note: si vous souhaitez utiliser la programmation réactive, encore mieux puisque les liaisons MVVM sont vraiment faciles à utiliser. Mais cet exemple concerne l'utilisation de MVVM sans programmation réactive.

Créons un exemple simple pour montrer comment utiliser MVVM.

Notre MVVMExampleViewController est un simple ViewController avec une étiquette et un bouton. Lorsque le bouton est enfoncé, le texte de l'étiquette doit être défini sur «Hello». Étant donné que décider de ce qu'il faut faire de l'interaction utilisateur-utilisateur fait partie de la logique métier, ViewModel devra décider quoi faire lorsque l'utilisateur appuie sur le bouton. La vue du MVVM ne devrait faire aucune logique métier.

class MVVMExampleViewController: UIViewController {
    
    @IBOutlet weak var helloLabel: UILabel!
    
    var viewModel: MVVMExampleViewModel?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func sayHelloButtonPressed(_ sender: UIButton) {
        viewModel?.userTriggeredSayHelloButton()
    }
}

MVVMExampleViewModel est un simple ViewModel.

class MVVMExampleViewModel {
    
    func userTriggeredSayHelloButton() {
        // How to update View's label when there is no reference to the View??
    }
}

Vous pourriez vous demander comment définir la référence de ViewModel dans la vue. Je le fais habituellement lorsque ViewController est en cours d'initialisation ou avant son affichage. Pour cet exemple simple, je ferais quelque chose comme ça dans AppDelegate :

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        if let rootVC = window?.rootViewController as? MVVMExampleViewController {
            let viewModel = MVVMExampleViewModel()
            rootVC.viewModel = viewModel
        }
        
        return true

La vraie question est maintenant la suivante: comment mettre à jour View à partir de ViewModel sans donner de référence à View to ViewModel? (Rappelez-vous que nous n'utiliserons aucune des bibliothèques iOS de programmation réactive)

Vous pourriez penser à utiliser KVO, mais cela ne ferait que compliquer les choses. Certaines personnes intelligentes ont réfléchi à la question et ont créé la bibliothèque Bond . La bibliothèque peut sembler compliquée et difficile à comprendre au début, alors je vais en prendre une petite partie et rendre notre MVVM entièrement fonctionnel.

Présentons la classe Dynamic qui est au cœur de notre modèle MVVM simple mais totalement fonctionnel.

class Dynamic<T> {
    typealias Listener = (T) -> Void
    var listener: Listener?
    
    func bind(_ listener: Listener?) {
        self.listener = listener
    }
    
    func bindAndFire(_ listener: Listener?) {
        self.listener = listener
        listener?(value)
    }
    
    var value: T {
        didSet {
            listener?(value)
        }
    }
    
    init(_ v: T) {
        value = v
    }
}

Dynamic classe Dynamic utilise Generics and Closures pour lier notre ViewModel à notre View. Je ne vais pas entrer dans les détails de cette classe, nous pouvons le faire dans les commentaires (pour raccourcir cet exemple). Nous allons maintenant mettre à jour nos MVVMExampleViewController et MVVMExampleViewModel pour utiliser ces classes.

Notre MVVMExampleViewController mis à jour

class MVVMExampleViewController: UIViewController {
    
    @IBOutlet weak var helloLabel: UILabel!
    
    var viewModel: MVVMExampleViewModel?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        bindViewModel()
    }
    
    func bindViewModel() {
        if let viewModel = viewModel {
            viewModel.helloText.bind({ (helloText) in
                DispatchQueue.main.async {
                    // When value of the helloText Dynamic variable
                    // is set or changed in the ViewModel, this code will
                    // be executed
                    self.helloLabel.text = helloText
                }
            })
        }
    }
    
    @IBAction func sayHelloButtonPressed(_ sender: UIButton) {
        viewModel?.userTriggeredSayHelloButton()
    }
}

MVVMExampleViewModel mis à jour:

    class MVVMExampleViewModel {
    
    // we have to initialize the Dynamic var with the
    // data type we want
    var helloText = Dynamic("")
    
    func userTriggeredSayHelloButton() {
        // Setting the value of the Dynamic variable
        // will trigger the closure we defined in the View
        helloText.value = "Hello"
    }
}

C'est ça. Votre ViewModel est maintenant capable de mettre à jour View sans avoir de référence à la View .

Ceci est un exemple très simple, mais je pense que vous avez une idée de son potentiel. Je ne vais pas entrer dans les détails concernant les avantages du MVVM, mais une fois que vous passerez de MVC à MVVM, vous ne reviendrez pas en arrière. Essayez-le et voyez par vous-même.