Szukaj…


MVVM bez programowania reaktywnego

Zacznę od bardzo krótkiego wyjaśnienia, co to jest i dlaczego warto używać wzorca projektowego Model-View-ViewModel (MVVM) w aplikacjach na iOS. Kiedy iOS pojawił się po raz pierwszy, Apple zaproponował użycie MVC (Model-View-Controller) jako wzorca projektowego. Pokazali to we wszystkich swoich przykładach i wszyscy pierwsi programiści byli zadowoleni z jego używania, ponieważ ładnie oddzieliło obawy między logiką biznesową a interfejsem użytkownika. Gdy aplikacje stały się większe i bardziej złożone, pojawił się nowy problem, odpowiednio nazwany Massive View Controllers (MVC). Ponieważ cała logika biznesowa została dodana do ViewController, z czasem stały się one zwykle zbyt duże i złożone. Aby uniknąć problemu z MVC, w świecie iOS wprowadzono nowy wzorzec projektowy - wzorzec Model-View-ViewModel (MVVM).

Diagram MVVM

Powyższy schemat pokazuje, jak wygląda MVVM. Masz standardowy ViewController + View (w serii ujęć, XIB lub Code), który działa jako widok MVVM (w późniejszym tekście - View będzie odwoływał się do widoku MVVM). Widok ma odniesienie do ViewModel, gdzie znajduje się nasza logika biznesowa. Ważne jest, aby zauważyć, że ViewModel nie wie nic o Widoku i nigdy nie ma odniesienia do widoku. ViewModel ma odniesienie do modelu.
To wystarczy z teoretyczną częścią MVVM. Więcej na ten temat można przeczytać tutaj .

Jednym z głównych problemów z MVVM jest sposób aktualizacji View za pomocą ViewModel, gdy ViewModel nie ma żadnych odniesień i nawet nie wie nic o View.

Główną częścią tego przykładu jest pokazanie, jak korzystać z MVVM (a dokładniej, jak powiązać ViewModel i View) bez żadnego reaktywnego programowania (ReactiveCocoa, ReactiveSwift lub RxSwif). Uwaga: jeśli chcesz korzystać z programowania reaktywnego, nawet lepiej, ponieważ powiązania MVVM są bardzo łatwe w użyciu. Ale ten przykład dotyczy sposobu korzystania z MVVM bez programowania reaktywnego.

Stwórzmy prosty przykład pokazujący, jak korzystać z MVVM.

Nasz MVVMExampleViewController to prosty ViewController z etykietą i przyciskiem. Po naciśnięciu przycisku tekst etykiety powinien być ustawiony na „Cześć”. Ponieważ podejmowanie decyzji dotyczących interakcji użytkownika jest częścią logiki biznesowej, ViewModel będzie musiał zdecydować, co zrobić, gdy użytkownik naciśnie przycisk. Widok MVVM nie powinien robić żadnej logiki biznesowej.

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

MVVMExampleViewModel to prosty ViewModel.

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

Można się zastanawiać, jak ustawić odniesienie ViewModel w widoku. Zwykle robię to, gdy ViewController jest inicjowany lub zanim zostanie wyświetlony. W tym prostym przykładzie zrobiłbym coś takiego w 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

Prawdziwe pytanie brzmi: jak zaktualizować View z ViewModel bez odniesienia do View do ViewModel? (Pamiętaj, że nie będziemy używać żadnej biblioteki Reactive Programming iOS)

Możesz pomyśleć o użyciu KVO, ale to zbytnio skomplikowałoby sprawę. Niektórzy mądrzy ludzie pomyśleli o tym i wymyślili bibliotekę Bonda . Biblioteka może wydawać się na początku skomplikowana i nieco trudniejsza do zrozumienia, więc wezmę tylko jedną małą jej część i sprawię, że nasz MVVM będzie w pełni funkcjonalny.

Przedstawiamy klasę Dynamic która jest rdzeniem naszego prostego, ale w pełni funkcjonalnego wzoru MVVM.

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

Klasa Dynamic używa generycznych i zamknięć, aby połączyć nasz ViewModel z naszym widokiem. Nie będę wchodził w szczegóły dotyczące tej klasy, możemy to zrobić w komentarzach (aby ten przykład był krótszy). Zaktualizujmy teraz nasz MVVMExampleViewController i MVVMExampleViewModel aby korzystać z tych klas.

Nasz zaktualizowany MVVMExampleViewController

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

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

To jest to. Twój ViewModel może teraz aktualizować View bez odniesienia do View .

To naprawdę prosty przykład, ale myślę, że masz pomysł, jak potężny może być. Nie będę wchodził w szczegóły na temat zalet MVVM, ale po przejściu z MVC na MVVM nie wrócisz. Po prostu spróbuj i przekonaj się sam.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow