Zoeken…
MVVM zonder reactieve programmering
Ik zal beginnen met een heel korte uitleg wat is en waarom het ontwerppatroon Model-View-ViewModel (MVVM) in uw iOS-apps gebruikt. Toen iOS voor het eerst verscheen, stelde Apple voor om MVC (Model-View-Controller) als ontwerppatroon te gebruiken. Ze toonden het in al hun voorbeelden en alle eerste ontwikkelaars waren blij het te gebruiken omdat het de zorgen tussen bedrijfslogica en gebruikersinterface goed scheidde. Naarmate applicaties groter en complexer werden, verscheen er een nieuw probleem, Massive View Controllers (MVC) genaamd. Omdat alle bedrijfslogica in de ViewController werd toegevoegd, werden ze na verloop van tijd meestal te groot en complex. Om MVC-problemen te voorkomen, werd een nieuw ontwerppatroon geïntroduceerd in de wereld van iOS - het model-View-ViewModel (MVVM) patroon.
Bovenstaand diagram laat zien hoe MVVM eruit ziet. U hebt een standaard ViewController + View (in storyboard, XIB of Code), die fungeert als MVVM's View (in latere tekst - View zal verwijzen naar MVVM's View). Een weergave verwijst naar een ViewModel, waar onze bedrijfslogica is. Het is belangrijk op te merken dat ViewModel niets over de weergave weet en nooit een verwijzing naar de weergave heeft. ViewModel heeft een verwijzing naar een model.
Dit is genoeg met een theoretisch deel van de MVVM. Meer hierover kun je hier lezen.
Een van de belangrijkste problemen met MVVM is hoe u View kunt bijwerken via ViewModel wanneer ViewModel geen referenties heeft en zelfs niets over de View weet.
Het belangrijkste deel van dit voorbeeld is om te laten zien hoe MVVM te gebruiken (meer precies, hoe ViewModel en View te binden) zonder reactieve programmering (ReactiveCocoa, ReactiveSwift of RxSwif). Even een opmerking: als je Reactive-programmering wilt gebruiken, nog beter, omdat MVVM-bindingen heel eenvoudig zijn om het te gebruiken. Maar dit voorbeeld gaat over het gebruik van MVVM zonder Reactief programmeren.
Laten we een eenvoudig voorbeeld maken om te demonstreren hoe MVVM te gebruiken.
Onze MVVMExampleViewController
is een eenvoudige ViewController met een label en een knop. Wanneer de knop wordt ingedrukt, moet de labeltekst worden ingesteld op 'Hallo'. Aangezien de beslissing over wat te doen met gebruikersinteractie deel uitmaakt van de bedrijfslogica, moet ViewModel beslissen wat te doen wanneer de gebruiker op de knop drukt. MVVM's View zou geen zakelijke logica moeten doen.
class MVVMExampleViewController: UIViewController {
@IBOutlet weak var helloLabel: UILabel!
var viewModel: MVVMExampleViewModel?
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func sayHelloButtonPressed(_ sender: UIButton) {
viewModel?.userTriggeredSayHelloButton()
}
}
MVVMExampleViewModel
is een eenvoudig ViewModel.
class MVVMExampleViewModel {
func userTriggeredSayHelloButton() {
// How to update View's label when there is no reference to the View??
}
}
Je vraagt je misschien af hoe je de referentie van het ViewModel in de View kunt instellen. Ik doe het meestal wanneer ViewController wordt geïnitialiseerd of voordat het wordt weergegeven. Voor dit eenvoudige voorbeeld zou ik zoiets in de 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
De echte vraag is nu: hoe View uit ViewModel bijwerken zonder een verwijzing naar de View naar het ViewModel te geven? (Vergeet niet dat we geen van de Reactive Programming iOS-bibliotheken zullen gebruiken)
Je zou kunnen overwegen om KVO te gebruiken, maar dat zou de zaken gewoon te ingewikkeld maken. Sommige slimme mensen hebben nagedacht over het probleem en kwamen met de Bond-bibliotheek . De bibliotheek lijkt in het begin misschien ingewikkeld en wat moeilijker te begrijpen, dus ik zal er maar een klein deel van nemen en onze MVVM volledig functioneel maken.
Laten we de Dynamic
klasse introduceren, die de kern vormt van ons eenvoudige maar volledig functionele MVVM-patroon.
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
Class gebruikt Generics and Closures om ons ViewModel te binden aan onze View. Ik zal niet ingaan op details over deze klasse, we kunnen het in de opmerkingen doen (om dit voorbeeld korter te maken). Laten we nu onze MVVMExampleViewController
en MVVMExampleViewModel
om die klassen te gebruiken.
Onze bijgewerkte 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()
}
}
Bijgewerkt 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"
}
}
Dat is het. Uw ViewModel
kan nu de View
bijwerken zonder dat deze een verwijzing naar de View
.
Dit is een heel eenvoudig voorbeeld, maar ik denk dat je een idee hebt hoe krachtig dit kan zijn. Ik zal niet ingaan op details over de voordelen van de MVVM, maar zodra u overschakelt van MVC naar MVVM, komt u niet meer terug. Probeer het gewoon en ontdek het zelf.