Comparaci贸n de las arquitecturas Viper y MVVM: c贸mo aplicar ambas



Actualmente, VIPER y MVVM son las soluciones arquitect贸nicas m谩s populares utilizadas en el desarrollo de grandes aplicaciones que requieren la participaci贸n en el desarrollo de grandes equipos que est谩n bien probados, respaldados a largo plazo y en constante evoluci贸n. En este art铆culo intentaremos aplicarlos en un peque帽o proyecto de prueba, que es una lista de contactos de usuarios con la capacidad de agregar un nuevo contacto. Este art铆culo tiene m谩s pr谩ctica que an谩lisis, y est谩 dirigido principalmente a aquellos que ya est谩n en teor铆a familiarizados con estas arquitecturas y ahora quisieran entender c贸mo funciona esto con ejemplos espec铆ficos. Sin embargo, una descripci贸n b谩sica de las arquitecturas y su comparaci贸n tambi茅n est谩 presente.


Este art铆culo es una traducci贸n del art铆culo de Rafael Sacchi "Comparaci贸n de las arquitecturas MVVM y Viper: cu谩ndo usar una u otra" . Desafortunadamente, en alg煤n momento de la creaci贸n del art铆culo, se cre贸 "publicaci贸n" en lugar de "traducci贸n", por lo que debe escribir aqu铆.

Una arquitectura bien dise帽ada es esencial para garantizar un soporte continuo para su proyecto. En este art铆culo, veremos las arquitecturas MVVM y VIPER como una alternativa al MVC tradicional.

MVC es un concepto bien conocido para todos aquellos que han estado involucrados en el desarrollo de software durante bastante tiempo. Este patr贸n divide el proyecto en tres partes: modelo que representa entidades; Ver, que es una interfaz para la interacci贸n del usuario; y Controlador, responsable de garantizar la interacci贸n entre Vista y Modelo. Esta es la arquitectura que Apple nos ofrece para usar en nuestras aplicaciones.

Sin embargo, probablemente sepa que los proyectos vienen con una funcionalidad bastante compleja: soporte para solicitudes de red, an谩lisis, acceso a modelos de datos, conversi贸n de datos para salida, reacci贸n a eventos de interfaz, etc. Como resultado, obtienes enormes controladores que resuelven los problemas anteriores y un mont贸n de c贸digo que no se puede reutilizar. En otras palabras, MVC puede ser una pesadilla para un desarrollador con soporte de proyectos a largo plazo. Pero, 驴c贸mo garantizar una alta modularidad y reutilizaci贸n en proyectos de iOS?

Veremos dos alternativas muy famosas a la arquitectura MVC: MVVM y VIPER. Ambos son bastante famosos en la comunidad iOS y han demostrado que pueden ser una gran alternativa a MVC. Hablaremos sobre su estructura, escribiremos una aplicaci贸n de ejemplo y consideraremos casos en los que es mejor usar una u otra arquitectura.

Ejemplo

Escribiremos una aplicaci贸n con una tabla de contactos de usuarios. Puede usar el c贸digo de este repositorio . En las carpetas de inicio, el esqueleto b谩sico del proyecto est谩 contenido, y en las carpetas finales hay una aplicaci贸n completamente terminada.

La aplicaci贸n tendr谩 dos pantallas: en la primera se mostrar谩 una lista de contactos en forma de tabla, la celda tendr谩 el nombre y el apellido del contacto, as铆 como la imagen base en lugar de la imagen del usuario.



La segunda pantalla es la pantalla para agregar un nuevo contacto, con los campos de entrada de nombre y apellido y los botones Listo y Cancelar.



MVVM

C贸mo funciona

MVVM significa Model-View-ViewModel . Este enfoque difiere de MVC en la l贸gica de distribuci贸n de responsabilidad entre m贸dulos.

  • Modelo : este m贸dulo no es diferente al de MVC. Es responsable de crear modelos de datos y puede contener l贸gica de negocios. Tambi茅n puede crear clases auxiliares, por ejemplo, como una clase de administrador para administrar objetos en Model y el administrador de red para procesar solicitudes de red y an谩lisis.
  • Ver : Y aqu铆 todo comienza a cambiar. El m贸dulo View en MVVM cubre la interfaz (subclases de archivos UIView, .xib y .storyboard), la l贸gica de visualizaci贸n (animaci贸n, renderizado) y el manejo de eventos del usuario (clics de botones, etc.) En MVC, View y Controller son responsables de esto. Esto significa que las vistas que tiene permanecer谩n sin cambios, mientras que ViewController contendr谩 una peque帽a parte de lo que hab铆a en MVC y, en consecuencia, disminuir谩 considerablemente.
  • ViewModel : ahora es el lugar donde se ubicar谩 la mayor parte del c贸digo que ten铆a anteriormente en ViewController. La capa ViewModel solicita datos del Modelo (puede ser una solicitud a una base de datos local o una solicitud de red) y los transfiere nuevamente a la Vista, en el formato en el que se utilizar谩 y se mostrar谩 all铆. Pero este es un mecanismo bidireccional, las acciones o los datos ingresados 鈥嬧媝or el usuario pasan a trav茅s del ViewModel y actualizan el Modelo. Dado que ViewModel realiza un seguimiento de todo lo que se muestra, es 煤til utilizar el mecanismo de enlace entre las dos capas.


En comparaci贸n con MVC, se est谩 moviendo de una arquitectura que se ve as铆:



Para el pr贸ximo varant de arquitectura:



En el que las clases y subclases de UIView y UIViewController se utilizan para implementar la Vista.

Bueno, ahora al grano. Escribamos un ejemplo de nuestra aplicaci贸n usando la arquitectura MVVM.

Aplicaci贸n de contactos MVVM

MODELO

La siguiente clase es un modelo de contacto de contacto:

import CoreData open class Contact: NSManagedObject { @NSManaged var firstName: String? @NSManaged var lastName: String? var fullName: String { get { var name = "" if let firstName = firstName { name += firstName } if let lastName = lastName { name += " \(lastName)" } return name } } } 


La clase de contacto tiene los campos firstName , lastName , as铆 como la propiedad calculada fullName .

VER

VIEW incluye: Storyboard principal, con vistas ya colocadas en 茅l; ContactsViewController, que muestra una lista de contactos en una tabla; y AddContactViewController con un par de etiquetas y campos de entrada para agregar el nombre y el apellido del nuevo contacto. Comencemos con el ContactsViewController . Su c贸digo se ver谩 as铆:

 import UIKit class ContactsViewController: UIViewController { @IBOutlet var tableView: UITableView! let contactViewModelController = ContactViewModelController() override func viewDidLoad() { super.viewDidLoad() tableView.tableFooterView = UIView() contactViewModelController.retrieveContacts({ [unowned self] in self.tableView.reloadData() }, failure: nil) } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { let addContactNavigationController = segue.destination as? UINavigationController let addContactVC = addContactNavigationController?.viewControllers[0] as? AddContactViewController addContactVC?.contactsViewModelController = contactViewModelController addContactVC?.didAddContact = { [unowned self] (contactViewModel, index) in let indexPath = IndexPath(row: index, section: 0) self.tableView.beginUpdates() self.tableView.insertRows(at: [indexPath], with: .left) self.tableView.endUpdates() } } } extension ContactsViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "ContactCell") as? ContactsTableViewCell guard let contactsCell = cell else { return UITableViewCell() } contactsCell.cellModel = contactViewModelController.viewModel(at: (indexPath as NSIndexPath).row) return contactsCell } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return contactViewModelController.contactsCount } } 


Incluso con una mirada superficial, est谩 claro que esta clase implementa en su mayor parte tareas de interfaz. Tambi茅n tiene navegaci贸n en el m茅todo prepareForSegue (: :) , y este es exactamente el momento que cambiar谩 en VIPER al agregar una capa de enrutador.

Echemos un vistazo m谩s de cerca a la extensi贸n de clase que implementa el protocolo UITableViewDataSource. Las funciones no funcionan directamente con el modelo de contacto del usuario de contacto en la capa Modelo; en su lugar, reciben datos (representados por la estructura ContactViewModel) en la forma en que se mostrar谩n, ya formateados con ViewModelController.

Lo mismo sucede en un circuito, que comienza inmediatamente despu茅s de crear un contacto. Su 煤nica tarea es agregar una fila a la tabla y actualizar la interfaz.

Ahora necesita establecer una relaci贸n entre la subclase de UITableViewCell y ViewModel. Esto se ver铆a como la clase de celda de la tabla ContactsTableViewCell :

 import UIKit class ContactsTableViewCell: UITableViewCell { var cellModel: ContactViewModel? { didSet { bindViewModel() } } func bindViewModel() { textLabel?.text = cellModel?.fullName } } 


Y tambi茅n lo es la clase AddContactViewController :

 import UIKit class AddContactViewController: UIViewController { @IBOutlet var firstNameTextField: UITextField! @IBOutlet var lastNameTextField: UITextField! var contactsViewModelController: ContactViewModelController? var didAddContact: ((ContactViewModel, Int) -> Void)? override func viewDidLoad() { super.viewDidLoad() firstNameTextField.becomeFirstResponder() } @IBAction func didClickOnDoneButton(_ sender: UIBarButtonItem) { guard let firstName = firstNameTextField.text, let lastName = lastNameTextField.text else { return } if firstName.isEmpty || lastName.isEmpty { showEmptyNameAlert() return } dismiss(animated: true) { [unowned self] in self.contactsViewModelController?.createContact(firstName: firstName, lastName: lastName, success: self.didAddContact, failure: nil) } } @IBAction func didClickOnCancelButton(_ sender: UIBarButtonItem) { dismiss(animated: true, completion: nil) } fileprivate func showEmptyNameAlert() { showMessage(title: "Error", message: "A contact must have first and last names") } fileprivate func showMessage(title: String, message: String) { let alertView = UIAlertController(title: title, message: message, preferredStyle: .alert) alertView.addAction(UIAlertAction(title: "Ok", style: .destructive, handler: nil)) present(alertView, animated: true, completion: nil) } } 


Y de nuevo, principalmente el trabajo con la interfaz de usuario est谩 sucediendo aqu铆. Tenga en cuenta que AddContactViewController delega la funcionalidad de creaci贸n de contactos al ViewModelController en la funci贸n didClickOnDoneButton (:) .

VER MODELO

Es hora de hablar sobre la capa ViewModel completamente nueva para nosotros. Primero, cree una clase de contacto ContactViewModel que proporcionar谩 la vista que necesitamos mostrar, y se definir谩n las funciones <and> con par谩metros para ordenar los contactos:

 public struct ContactViewModel { var fullName: String } public func <(lhs: ContactViewModel, rhs: ContactViewModel) -> Bool { return lhs.fullName.lowercased() < rhs.fullName.lowercased() } public func >(lhs: ContactViewModel, rhs: ContactViewModel) -> Bool { return lhs.fullName.lowercased() > rhs.fullName.lowercased() } 


El c贸digo de ContactViewModelController se ver谩 as铆:

 class ContactViewModelController { fileprivate var contactViewModelList: [ContactViewModel] = [] fileprivate var dataManager = ContactLocalDataManager() var contactsCount: Int { return contactViewModelList.count } func retrieveContacts(_ success: (() -> Void)?, failure: (() -> Void)?) { do { let contacts = try dataManager.retrieveContactList() contactViewModelList = contacts.map() { ContactViewModel(fullName: $0.fullName) } success?() } catch { failure?() } } func viewModel(at index: Int) -> ContactViewModel { return contactViewModelList[index] } func createContact(firstName: String, lastName: String, success: ((ContactViewModel, Int) -> Void)?, failure: (() -> Void)?) { do { let contact = try dataManager.createContact(firstName: firstName, lastName: lastName) let contactViewModel = ContactViewModel(fullName: contact.fullName) let insertionIndex = contactViewModelList.insertionIndex(of: contactViewModel) { $0 < $1 } contactViewModelList.insert(contactViewModel, at: insertionIndex) success?(contactViewModel, insertionIndex) } catch { failure?() } } } 


Nota: MVVM no proporciona una definici贸n exacta de c贸mo crear un ViewModel. Cuando quiero crear una arquitectura m谩s estratificada, prefiero crear un ViewModelController que interactuar谩 con la capa Modelo y ser谩 responsable de crear los objetos ViewModel.

Lo principal que es muy f谩cil de recordar: la capa ViewModel no debe participar en el trabajo con la interfaz de usuario. Para evitar esto, es mejor no importar nunca UIKit a un archivo con ViewModel.

La clase ContactViewModelController solicita contactos del almacenamiento local e intenta no afectar la capa Modelo. Devuelve los datos en el formato que la vista requiere que se muestre y notifica la vista cuando se agrega un nuevo contacto y los datos cambian.

En la vida real, esto ser铆a una solicitud de red, y no una solicitud a la base de datos local, pero en ninguno de los casos deber铆a ser parte de ViewModel; tanto el trabajo en red como el trabajo con la base de datos local deben proporcionarse utilizando sus propios administradores ( gerentes).

Eso es todo sobre MVVM. Quiz谩s este enfoque le parezca m谩s comprobable, compatible y distribuido que MVC. Ahora hablemos de VIPER y veamos c贸mo se diferencia de MVVM.

VIPER

C贸mo funciona

VIPER es una implementaci贸n de arquitectura limpia para proyectos de iOS. Su estructura consiste en: Vista, Interactor, Presentador, Entidad y Enrutador. Esta es realmente una arquitectura muy distribuida y modular que le permite compartir la responsabilidad, est谩 muy bien cubierta por las pruebas unitarias y hace que su c贸digo sea reutilizable.

  • Ver : Una capa de interfaz que generalmente implica archivos UIKit (incluido el UIViewController). Es comprensible que en sistemas m谩s distribuidos, las subclases de UIViewController se relacionen con View. En VIPER, las cosas son casi lo mismo que en MVVM: View es responsable de mostrar lo que Presenter proporciona y de transmitir informaci贸n o acciones ingresadas por el usuario a Presenter.
  • Interactor : contiene la l贸gica de negocios necesaria para que la aplicaci贸n funcione. Interactor es responsable de recuperar datos del Modelo (solicitudes locales o de red) y su implementaci贸n no est谩 relacionada de ninguna manera con la interfaz de usuario. Es importante recordar que los administradores locales y de red no son parte de VIPER, sino que se tratan como dependencias separadas.
  • Presentador : responsable de formatear los datos para mostrar en la vista. En MVVM en nuestro ejemplo, ViewModelController fue responsable de esto. Presenter recibe datos de Interactor, crea una instancia de ViewModel (una clase formateada para una visualizaci贸n correcta) y la pasa a View. Tambi茅n responde a la entrada de datos del usuario, solicita datos adicionales de la base de datos o viceversa, se los pasa.
  • Entidad : Forma parte de la responsabilidad de la capa Modelo, que se usa en otras arquitecturas. La entidad es un objeto de datos simple, sin l贸gica empresarial, administrado por un tractor en l铆nea y varios administradores de datos.
  • Enrutador : toda la l贸gica de navegaci贸n de la aplicaci贸n. Puede parecer que esta no es la capa m谩s importante, pero si necesita, por ejemplo, reutilizar la misma vista tanto en el iPhone como en la aplicaci贸n para iPad, lo 煤nico que puede cambiar es c贸mo aparecen sus vistas en la pantalla. Esto le permite no tocar m谩s capas, excepto el enrutador, que ser谩 responsable de esto en cada caso.


En comparaci贸n con MVVM, VIPER tiene varias diferencias clave en la distribuci贸n de responsabilidad:

- 茅l tiene un enrutador, una capa separada responsable de la navegaci贸n

- Las entidades son simples objetos de datos, que redistribuyen la responsabilidad de acceder a los datos del Modelo al Interactor.

- Las responsabilidades de ViewModelController se comparten entre Interactor y Presenter

Y ahora repitamos la misma aplicaci贸n, pero ya en VIPER. Pero para facilitar la comprensi贸n, solo haremos un controlador con contactos. Puede encontrar el c贸digo del controlador para agregar un nuevo contacto en el proyecto utilizando el enlace (carpeta de inicio de contactos VIPER en este repositorio ).

Nota : Si decide realizar su proyecto en VIPER, entonces no debe intentar crear todos los archivos manualmente; puede usar uno de los generadores de c贸digo, por ejemplo, como VIPER Gen o Generamba (proyecto Rambler) .

Aplicaci贸n de contactos VIPER

VER

VIEW est谩 representado por elementos de Main.storyboard y la clase ContactListView. VIEW es muy pasivo; sus 煤nicas tareas son transferir eventos de interfaz al presentador y actualizar su estado, previa notificaci贸n del presentador. As铆 es como se ve el c贸digo ContactListView :

 import UIKit class ContactListView: UIViewController { @IBOutlet var tableView: UITableView! var presenter: ContactListPresenterProtocol? var contactList: [ContactViewModel] = [] override func viewDidLoad() { super.viewDidLoad() presenter?.viewDidLoad() tableView.tableFooterView = UIView() } @IBAction func didClickOnAddButton(_ sender: UIBarButtonItem) { presenter?.addNewContact(from: self) } } extension ContactListView: ContactListViewProtocol { func reloadInterface(with contacts: [ContactViewModel]) { contactList = contacts tableView.reloadData() } func didInsertContact(_ contact: ContactViewModel) { let insertionIndex = contactList.insertionIndex(of: contact) { $0 < $1 } contactList.insert(contact, at: insertionIndex) let indexPath = IndexPath(row: insertionIndex, section: 0) tableView.beginUpdates() tableView.insertRows(at: [indexPath], with: .right) tableView.endUpdates() } } extension ContactListView: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "ContactCell") else { return UITableViewCell() } cell.textLabel?.text = contactList[(indexPath as NSIndexPath).row].fullName return cell } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return contactList.count } } 


Ver env铆a los eventos viewDidLoad y didClickOnAddButton al presentador. En el primer evento, el presentador solicitar谩 datos de Interactor, y en el segundo, el presentador le pedir谩 al enrutador que cambie al controlador para agregar un nuevo contacto.

Los m茅todos de protocolo ContactListViewProtocol se llaman desde Presenter cuando se solicita una lista de contactos o cuando se agrega un nuevo contacto. En cualquier caso, los datos en la Vista contienen solo la informaci贸n necesaria para la visualizaci贸n.

Tambi茅n en la Vista hay m茅todos que implementan el protocolo UITableViewDataSource que puebla la tabla con los datos recibidos.

INTERACTOR

Interactor en nuestro ejemplo es bastante simple. Todo lo que hace es solicitar datos a trav茅s del administrador de la base de datos local, y no le importa lo que use este administrador, CoreData, Realm o cualquier otra soluci贸n. El c贸digo en ContactListInteractor ser谩 el siguiente:

 class ContactListInteractor: ContactListInteractorInputProtocol { weak var presenter: ContactListInteractorOutputProtocol? var localDatamanager: ContactListLocalDataManagerInputProtocol? func retrieveContacts() { do { if let contactList = try localDatamanager?.retrieveContactList() { presenter?.didRetrieveContacts(contactList) } else { presenter?.didRetrieveContacts([]) } } catch { presenter?.didRetrieveContacts([]) } } } 


Despu茅s de que Interactor recibe los datos solicitados, notifica al presentador. Adem谩s, como opci贸n, Interactor puede transmitir un error al presentador, que luego tendr谩 que formatear el error en una vista adecuada para mostrar en la vista.

Nota : Como habr谩s notado, cada capa en VIPER implementa un protocolo. Como resultado, las clases dependen de abstracciones, y no de una implementaci贸n particular, cumpliendo as铆 el principio de inversi贸n de dependencia (uno de los principios de SOLID).

Presentador

El elemento m谩s importante de la arquitectura. Toda la comunicaci贸n entre la Vista y el resto de las capas (Interactor y Enrutador) pasa por el Presentador. ContactListPresenter Code:

 class ContactListPresenter: ContactListPresenterProtocol { weak var view: ContactListViewProtocol? var interactor: ContactListInteractorInputProtocol? var wireFrame: ContactListWireFrameProtocol? func viewDidLoad() { interactor?.retrieveContacts() } func addNewContact(from view: ContactListViewProtocol) { wireFrame?.presentAddContactScreen(from: view) } } extension ContactListPresenter: ContactListInteractorOutputProtocol { func didRetrieveContacts(_ contacts: [Contact]) { view?.reloadInterface(with: contacts.map() { return ContactViewModel(fullName: $0.fullName) }) } } extension ContactListPresenter: AddModuleDelegate { func didAddContact(_ contact: Contact) { let contactViewModel = ContactViewModel(fullName: contact.fullName) view?.didInsertContact(contactViewModel) } func didCancelAddContact() {} } 


Despu茅s de cargar View, notifica a Presenter, que a su vez solicita datos a trav茅s de Interactor. Cuando el usuario hace clic en el bot贸n Agregar nuevo contacto, Ver notifica al presentador, que env铆a una solicitud para abrir la pantalla Agregar nuevo contacto en el enrutador.

Presenter tambi茅n formatea los datos y los devuelve a la Vista despu茅s de consultar la lista de contactos. Tambi茅n es responsable de implementar el protocolo AddModuleDelegate. Esto significa que Presenter recibir谩 una notificaci贸n cuando se agregue un nuevo contacto, prepare los datos del contacto para mostrar y transfiera a Ver.

Como habr谩s notado, Presenter tiene muchas posibilidades de volverse bastante engorroso. Si existe tal posibilidad, el Presentador se puede dividir en dos partes: el Presentador, que solo recibe datos, los formatea para mostrarlos y los pasa a Ver; y un controlador de eventos que responder谩 a las acciones del usuario.

ENTIDAD

Esta capa es similar a la capa Modelo en MVVM. En nuestra aplicaci贸n, est谩 representado por la clase de contacto y las funciones de definici贸n de operador <y>. El contenido del contacto se ver谩 as铆:

 import CoreData open class Contact: NSManagedObject { var fullName: String { get { var name = "" if let firstName = firstName { name += firstName } if let lastName = lastName { name += " " + lastName } return name } } } public struct ContactViewModel { var fullName = "" } public func <(lhs: ContactViewModel, rhs: ContactViewModel) -> Bool { return lhs.fullName.lowercased() < rhs.fullName.lowercased() } public func >(lhs: ContactViewModel, rhs: ContactViewModel) -> Bool { return lhs.fullName.lowercased() > rhs.fullName.lowercased() } 


ContactViewModel contiene los campos que presenta el presentador (formatos) que muestra la vista. La clase Contact es una subclase de NSManagedObject que contiene los mismos campos que en el modelo CoreData.

ROUTER

Y finalmente, la 煤ltima, pero ciertamente no importante, capa. Toda la responsabilidad de la navegaci贸n recae en Presenter y WireFrame. El presentador recibe un evento del usuario y sabe cu谩ndo hacer la transici贸n, y WireFrame sabe c贸mo y d贸nde hacer esta transici贸n. Para que no se confunda, en este ejemplo, la capa Router est谩 representada por la clase ContactListWireFrame y se denomina WireFrame en el texto. ContactListWireFrame Code:

 import UIKit class ContactListWireFrame: ContactListWireFrameProtocol { class func createContactListModule() -> UIViewController { let navController = mainStoryboard.instantiateViewController(withIdentifier: "ContactsNavigationController") if let view = navController.childViewControllers.first as? ContactListView { let presenter: ContactListPresenterProtocol & ContactListInteractorOutputProtocol = ContactListPresenter() let interactor: ContactListInteractorInputProtocol = ContactListInteractor() let localDataManager: ContactListLocalDataManagerInputProtocol = ContactListLocalDataManager() let wireFrame: ContactListWireFrameProtocol = ContactListWireFrame() view.presenter = presenter presenter.view = view presenter.wireFrame = wireFrame presenter.interactor = interactor interactor.presenter = presenter interactor.localDatamanager = localDataManager return navController } return UIViewController() } static var mainStoryboard: UIStoryboard { return UIStoryboard(name: "Main", bundle: Bundle.main) } func presentAddContactScreen(from view: ContactListViewProtocol) { guard let delegate = view.presenter as? AddModuleDelegate else { return } let addContactsView = AddContactWireFrame.createAddContactModule(with: delegate) if let sourceView = view as? UIViewController { sourceView.present(addContactsView, animated: true, completion: nil) } } } 


Como WireFrame es responsable de crear el m贸dulo, ser谩 conveniente configurar todas las dependencias aqu铆. Cuando desee abrir otro controlador, la funci贸n que abre el nuevo controlador recibe como argumento el objeto que lo abrir谩, y crea un nuevo controlador utilizando su WireFrame. Adem谩s, al crear un nuevo controlador, los datos necesarios se transfieren a 茅l, en este caso solo el delegado (Presentador del controlador con contactos) para recibir el contacto creado.

La capa de enrutador ofrece una buena oportunidad para evitar el uso de segues (transiciones) en guiones gr谩ficos y organizar todo el c贸digo de navegaci贸n. Dado que los guiones gr谩ficos no proporcionan una soluci贸n compacta para transferir datos entre controladores, nuestra implementaci贸n de navegaci贸n no agregar谩 c贸digo adicional. Todo lo que obtenemos es solo la mejor reutilizaci贸n.


Resumen :

Puede encontrar ambos proyectos en este repositorio .

Como puede ver, MVVM y VIPER, aunque diferentes, no son 煤nicos. MVVM nos dice que adem谩s de Ver y Modelo, tambi茅n deber铆a haber una capa ViewModel. Pero no se dice nada sobre c贸mo se debe crear esta capa, ni sobre c贸mo se solicitan los datos: la responsabilidad de esta capa no est谩 claramente definida. Hay muchas formas de implementarlo y puede usar cualquiera de ellas.

VIPER, por otro lado, es una arquitectura bastante 煤nica. Consiste en muchas capas, cada una de las cuales tiene un 谩rea de responsabilidad bien definida y menos de MVVM est谩 influenciada por el desarrollador.

Cuando se trata de elegir una arquitectura, generalmente no existe la 煤nica soluci贸n correcta, pero a煤n as铆 intentar茅 dar algunos consejos. Si tiene un proyecto grande y largo, con requisitos claros y desea tener muchas oportunidades para reutilizar componentes, entonces VIPER ser谩 la mejor soluci贸n. Una delimitaci贸n m谩s clara de la responsabilidad hace posible organizar mejor las pruebas y mejorar la reutilizaci贸n.

Source: https://habr.com/ru/post/440904/


All Articles