Buscar..
MVVM sin programación reactiva
Comenzaré con una explicación muy breve de lo que es y por qué usar el patrón de diseño Model-View-ViewModel (MVVM) en sus aplicaciones iOS. Cuando apareció iOS por primera vez, Apple sugirió usar MVC (Model-View-Controller) como patrón de diseño. Lo mostraron en todos sus ejemplos y todos los primeros desarrolladores se alegraron de usarlo porque separaba muy bien las preocupaciones entre la lógica de negocios y la interfaz de usuario. A medida que las aplicaciones se hacían más grandes y complejas, apareció un nuevo problema llamado Massive View Controllers (MVC). Debido a que toda la lógica de negocios se agregó en el ViewController, con el tiempo por lo general se volvieron demasiado grandes y complejos. Para evitar el problema de MVC, se introdujo un nuevo patrón de diseño en el mundo de iOS: Model-View-ViewModel (MVVM).
El diagrama de arriba muestra cómo se ve MVVM. Tiene un ViewController + View estándar (en el guión gráfico, XIB o Code), que actúa como Vista de MVVM (en el texto posterior: Vista hará referencia a la Vista de MVVM). Una vista tiene una referencia a un ViewModel, donde está nuestra lógica de negocios. Es importante notar que ViewModel no sabe nada sobre la Vista y nunca tiene una referencia a la vista. ViewModel tiene una referencia a un modelo.
Esto es suficiente con una parte teórica de la MVVM. Más sobre esto se puede leer aquí .
Uno de los principales problemas con MVVM es cómo actualizar la Vista a través del ViewModel cuando ViewModel no tiene referencias y ni siquiera sabe nada sobre la Vista.
La parte principal de este ejemplo es mostrar cómo usar MVVM (más precisamente, cómo enlazar ViewModel y View) sin ninguna programación reactiva (ReactiveCocoa, ReactiveSwift o RxSwif). Solo como una nota: si desea usar la programación reactiva, incluso mejor, ya que los enlaces MVVM se hacen realmente fáciles de usar. Pero este ejemplo es sobre cómo usar MVVM sin programación reactiva.
Vamos a crear un ejemplo simple para demostrar cómo usar MVVM.
Nuestro MVVMExampleViewController
es un ViewController simple con una etiqueta y un botón. Cuando se presiona el botón, el texto de la etiqueta debe establecerse en 'Hola'. Desde que decidir qué hacer con la interacción del usuario con el usuario es parte de la lógica empresarial, ViewModel tendrá que decidir qué hacer cuando el usuario presiona el botón. La vista de MVVM no debería hacer ninguna lógica de negocios.
class MVVMExampleViewController: UIViewController {
@IBOutlet weak var helloLabel: UILabel!
var viewModel: MVVMExampleViewModel?
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func sayHelloButtonPressed(_ sender: UIButton) {
viewModel?.userTriggeredSayHelloButton()
}
}
MVVMExampleViewModel
es un ViewModel simple.
class MVVMExampleViewModel {
func userTriggeredSayHelloButton() {
// How to update View's label when there is no reference to the View??
}
}
Podría preguntarse cómo establecer la referencia de ViewModel en la Vista. Generalmente lo hago cuando se está inicializando ViewController o antes de que se muestre. Para este ejemplo simple, haría algo como esto en el 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 verdadera pregunta ahora es: ¿cómo actualizar View from ViewModel sin dar una referencia a View to the ViewModel? (Recuerda, no usaremos ninguna de las bibliotecas de React Programming iOS)
Se podría pensar en usar KVO, pero eso solo complicaría las cosas demasiado. Algunas personas inteligentes han pensado en el tema y han creado la biblioteca de Bond . La biblioteca puede parecer complicada y un poco más difícil de entender al principio, así que solo tomaré una pequeña parte de ella y haré que nuestra MVVM sea completamente funcional.
Introduzcamos la clase Dynamic
, que es el núcleo de nuestro patrón MVVM simple pero completamente funcional.
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
clase Dynamic
usa genéricos y cierres para vincular nuestro ViewModel con nuestra vista. No entraré en detalles sobre esta clase, podemos hacerlo en los comentarios (para hacer este ejemplo más corto). MVVMExampleViewController
ahora nuestro MVVMExampleViewController
y MVVMExampleViewModel
para usar esas clases.
Nuestro MVVMExampleViewController
actualizado
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
actualizado:
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"
}
}
Eso es. Su ViewModel
ahora puede actualizar la View
sin que tenga una referencia a la View
.
Este es un ejemplo muy simple, pero creo que tienes una idea de lo poderoso que puede ser esto. No voy a entrar en detalles sobre los beneficios de MVVM, pero una vez que cambie de MVC a MVVM, no volverá. Solo inténtalo y verás por ti mismo.