');

iOS MVVM senza programmazione reattiva

Esempio

Inizierò con una spiegazione molto breve di cosa è e perché utilizzare il modello di progettazione Model-View-ViewModel (MVVM) nelle tue app iOS. Quando è comparso iOS per la prima volta, Apple ha suggerito di utilizzare MVC (Model-View-Controller) come modello di progettazione. Lo hanno mostrato in tutti i loro esempi e tutti i primi sviluppatori sono stati felici di utilizzarlo perché ha separato bene le preoccupazioni tra la business logic e l'interfaccia utente. Man mano che le applicazioni diventavano più grandi e complesse, un nuovo problema appariva appropriatamente chiamato Massive View Controller (MVC). Poiché tutta la logica aziendale è stata aggiunta nel ViewController, con il tempo sono diventati di solito troppo grandi e complessi. Per evitare il problema di MVC, è stato introdotto un nuovo modello di progettazione per il mondo di iOS: modello Model-View-ViewModel (MVVM).

MVVM Diagram

Lo schema sopra mostra come appare MVVM. Hai un ViewController + View standard (nello storyboard, XIB o Code), che funge da Vista MVVM (nel testo successivo - View farà riferimento a MVVM's View). Una vista ha un riferimento a un ViewModel, dove è la nostra logica di business. È importante notare che ViewModel non sa nulla della vista e non ha mai un riferimento alla vista. ViewModel ha un riferimento a un modello.
Questo è sufficiente con una parte teorica di MVVM. Maggiori informazioni possono essere lette qui .

Uno dei problemi principali di MVVM è come aggiornare View tramite ViewModel quando ViewModel non ha riferimenti e non sa nulla della Vista.

La parte principale di questo esempio è mostrare come utilizzare MVVM (più precisamente, come associare ViewModel e View) senza alcuna programmazione reattiva (ReactiveCocoa, ReactiveSwift o RxSwif). Come nota: se si desidera utilizzare la programmazione reattiva, ancora meglio visto che i collegamenti MVVM sono fatti davvero facilmente. Ma questo esempio è su come usare MVVM senza programmazione Reattiva.

Creiamo un semplice esempio per dimostrare come utilizzare MVVM.

Il nostro MVVMExampleViewController è un semplice ViewController con un'etichetta e un pulsante. Quando viene premuto il pulsante, il testo dell'etichetta deve essere impostato su "Ciao". Dal momento che decidere cosa fare nell'interazione utente dell'utente fa parte della logica aziendale, ViewModel dovrà decidere cosa fare quando l'utente preme il pulsante. MVVM's View non dovrebbe fare alcuna logica di business.

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

MVVMExampleViewModel è un semplice ViewModel.

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

Potresti chiederti come impostare il riferimento ViewModel nella vista. Solitamente lo faccio quando ViewController viene inizializzato o prima che venga mostrato. Per questo semplice esempio, farei qualcosa di simile in 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 vera domanda ora è: come aggiornare View from ViewModel senza dare un riferimento alla View to the ViewModel? (Ricorda, non useremo nessuna delle librerie iOS di Programmazione Reattiva)

Potresti pensare di usare KVO, ma questo complicherebbe le cose troppo. Alcune persone intelligenti hanno pensato al problema e hanno inventato la biblioteca di James Bond . La libreria potrebbe sembrare complicata e un po 'più difficile da capire all'inizio, quindi ne prenderò solo una piccola parte e renderò il nostro MVVM pienamente funzionante.

Introduciamo la classe Dynamic , che è il nucleo del nostro modello MVVM semplice ma pienamente funzionale.

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 utilizza Generics and Closures per associare il nostro ViewModel alla nostra vista. Non entrerò nei dettagli di questa classe, possiamo farlo nei commenti (per rendere questo esempio più breve). MVVMExampleViewController ora il nostro MVVMExampleViewController e MVVMExampleViewModel per utilizzare tali classi.

Il nostro MVVMExampleViewController aggiornato

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

Aggiornato MVVMExampleViewModel :

    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"
    }
}

È così. ViewModel è ora in grado di aggiornare View senza che abbia un riferimento alla View .

Questo è un esempio molto semplice, ma penso che tu abbia un'idea di quanto possa essere potente. Non entrerò nei dettagli sui vantaggi di MVVM, ma una volta passati da MVC a MVVM, non tornerai indietro. Provalo e vedi di persona.