Sök…


MVVM utan reaktiv programmering

Jag börjar med en riktigt kort förklaring vad som är och varför använda Model-View-ViewModel (MVVM) designmönster i dina iOS-appar. När iOS först kom föreslog Apple att använda MVC (Model-View-Controller) som ett designmönster. De visade det i alla sina exempel och alla första utvecklare var nöjda med att använda det eftersom det fint skilde oro mellan affärslogik och användargränssnitt. När applikationerna blev större och mer komplexa, dök upp ett nytt problem på lämpligt sätt kallad Massive View Controllers (MVC). Eftersom all affärslogik lades till i ViewController blev de med tiden för stora och komplicerade. För att undvika MVC-problem introducerades ett nytt designmönster till iOS-världen - Model-View-ViewModel (MVVM) -mönster.

MVVM-diagram

Diagrammet ovan visar hur MVVM ser ut. Du har en standard ViewController + View (i storyboard, XIB eller Code), som fungerar som MVVM's View (i senare text - View kommer att referera till MVVM's View). En vy har en referens till en ViewModel, där vår affärslogik är. Det är viktigt att märka att ViewModel inte vet någonting om vyn och aldrig har någon hänvisning till vyn. ViewModel har en referens till en modell.
Detta räcker med en teoretisk del av MVVM. Mer om det kan läsas här .

Ett av huvudproblemen med MVVM är hur man uppdaterar View via ViewModel när ViewModel inte har några referenser och inte ens vet något om vyn.

Huvuddelen av detta exempel är att visa hur man använder MVVM (mer exakt hur man binder ViewModel och View) utan någon reaktiv programmering (ReactiveCocoa, ReactiveSwift eller RxSwif). Precis som en anmärkning: om du vill använda reaktiv programmering, ännu bättre eftersom MVVM-bindningar görs riktigt enkelt med det. Men det här exemplet handlar om hur man använder MVVM utan reaktiv programmering.

Låt oss skapa ett enkelt exempel för att visa hur man använder MVVM.

Vår MVVMExampleViewController är en enkel ViewController med en etikett och en knapp. När knappen trycks in ska etiketteksten ställas in på "Hej". Eftersom beslut om vad man ska göra för användarens användarinteraktion är en del av affärslogiken måste ViewModel bestämma vad man ska göra när användaren trycker på knappen. MVVM's View bör inte göra någon affärslogik.

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

MVVMExampleViewModel är en enkel ViewModel.

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

Du kanske undrar hur man ställer in ViewModels referens i vyn. Jag gör det vanligtvis när ViewController initieras eller innan det kommer att visas. För detta enkla exempel skulle jag göra något liknande i 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

Den verkliga frågan är nu: hur man uppdaterar View från ViewModel utan att hänvisa till View till ViewModel? (Kom ihåg att vi inte kommer att använda någon av de reaktiva programmerings iOS-biblioteken)

Du kan tänka på att använda KVO, men det skulle bara komplicera saker för mycket. Vissa smarta människor har tänkt på frågan och kommit fram till Bond-biblioteket . Biblioteket kanske verkar komplicerat och lite svårare att förstå till en början, så jag tar bara en liten del av det och gör vår MVVM fullt funktionell.

Låt oss introducera den Dynamic klassen som är kärnan i vårt enkla men fullt funktionella MVVM-mönster.

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 klass använder Generics and Closures för att binda vår ViewModel med vår View. Jag kommer inte att gå in på detaljer om den här klassen, vi kan göra det i kommentarerna (för att göra detta exempel kortare). Låt oss nu uppdatera vår MVVMExampleViewController och MVVMExampleViewModel att använda dessa klasser.

Vår uppdaterade 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()
    }
}

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

Nu räcker det. Din ViewModel nu uppdatera View utan att ha någon hänvisning till View .

Detta är ett riktigt enkelt exempel, men jag tror att du har en aning om hur kraftfullt det här kan vara. Jag kommer inte gå in på detaljer om fördelarna med MVVM, men när du byter från MVC till MVVM kommer du inte tillbaka. Prova bara och se själv.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow