El tema de las arquitecturas para el desarrollo de aplicaciones siempre ha sido muy importante aunque a veces le quitemos importancia. Existen muchas arquitecturas de desarrollo pero entre las más conocidas están MVC y VIPER. MVC es una arquitectura simple para desarrollar aplicaciones con velocidad y que no vayan a tener un amplio futuro de crecimiento. Sin embargo VIPER se usa para aplicaciones con amplia escalabilidad ya que esta arquitectura respeta los principios SOLID haciendo especial hincapié en el principio de simple responsabilidad.
¿Qué es la arquitectura VIPER?
Una arquitectura de software que respeta al máximo el principio de simple responsabilidad de SOLID. Sirve para hacer una aplicación más modular y fácil de mantener. Esta arquitectura está totalmente orientada a protocolos que es algo que deberíamos controlar en Swift.
Un módulo de VIPER tiene unos componentes que lo forman: View, Presenter, Interactor, Entity y Router.
Esta separación del modelo y la vista provocan una abstracción que permite que una aplicación de gran volumen sea fácil de mantener y limpia. Empezaré con un orden de menor a mayor ya que me parece más fácil de entender.
1 – Entity
Este es sin duda el componente más básico de VIPER ya que no debería tener nada más que propiedades, claro está sin relación con vistas ni ningún otro componente que no sea un entity.
import Foundation class Person: NSObject { var name:String! var age:Int! {
2 – Interactor
El componente del interactor tiene una responsabilidad básica aunque importante. Obtiene los datos de los web services o base de datos, crea los objetos de las entidades o entity y se los proporciona al presenter. Es importante destacar que el interactor no tiene ninguna relación con la vista, de hecho no debería siquiera importar a UIKit, con Foundation debería ser suficiente. Tampoco debe tener ninguna lógica más que la necesaria para formar los entity y obtener los datos.
import Foundation import Alamofire protocol PersonsInteractorInput: class { func fetchPersons() } class PersonsInteractor: NSObject, PersonsInteractorInput { let url = "https://www.example.com" weak var output: PersonsInteractorOutput! func fetchPersons() { Alamofire.request(.GET, url).responseArray { (response: Response) in let personsArray:[Person] = response.result.value! self.output.personsFetched(personsArray!) } } }
3 – Presenter
El presenter es el componente más importante de VIPER junto con el interactor ya que actúa como puente entre los módulos de VIPER y contiene la lógica de negocio. Recibe los eventos de la vista y reacciona a ellos pidiendo los datos necesarios al interactor. En sentido opuesto recibe los datos del interactor, aplica la lógica y prepara el contenido para pasárselo a la vista y que esta lo muestre.
import Foundation // Protocolo que define los comandos mandados desde la vista al presenter. protocol PersonsModuleInterface: class { func updateView() func showDetailsForPerson(_ person: Person) } // Protocolo que define los comandos mandados desde el interactor al presenter. protocol PersonsInteractorOutput: class { func personsFetched(_ persons: [Person]) } class PersonsPresenter: NSObject, PersonsInteractorOutput, PersonsModuleInterface { // Referencia a la vista (weak para evitar un retain cycle) weak var view: PersonsViewInterface! // Referencia a la interfaz del interactor var interactor: PersonsInteractorInput! // Referencia al router var wireframe: PersonsWireframe! // MARK: PersonsModuleInterface func updateView() { self.interactor.fetchPersons() } func showDetailsForPerson(_ person: Person) { self.wireframe.presentDetailsInterfaceForPerson(person) } // MARK: PersonsInteractorOutput func personsFetched(_ persons: [Person]) { if persons.count > 0 { self.view.showPersonsData(persons) } else { self.view.showNoContentScreen() } } }
4 – View
Básicamente es un ViewController que contiene sub vistas implementadas programaticamente o mediante XIB. La vista tiene como única responsabilidad mostrar en la interfaz la información que llega desde el presenter y recoger eventos del usuario delegándolos al presentador.
import UIKit protocol PersonsViewInterface: class { func showPersonsData(persons: [Person]) func showNoContentScreen() } class PersonsViewController: UIViewController, PersonsViewInterface { @IBOutlet weak var table: UITableView! var persons:[Person] = [] var presenter: PersonsModuleInterface! override func viewDidLoad() { super.viewDidLoad() self.presenter.updateView() } // MARK: PersonsViewInterface func showPersonsData(persons: [Person]) { self.persons = persons self.tableView.reloadData() } func showNoContentScreen() { // Mostrar pantalla sin información } }
5.- Router
Es el encargado de la navegación y de pasar datos entre vistas. Debe implementar un protocolo que incluya todas las posibilidades de navegación entre módulos. Debido a como es el sistema de iOS para realizar la navegación el router tiene que conocer a la view (ViewController)
import UIKit // Protocolo que define las posibles navegaciones para el módulo de personas. protocol PersonsWireframeInput { func presentDetailsInterfaceForPerson(person: Person) } class PersonsWireframe : NSObject, PersonsWireframeInput { // Referencia al ViewController (weak para evitar un retain cycle) weak var personsViewController: PersonsViewController! // Referencia a el router del siguiente módulo de VIPER var detailsWireframe: DetailsWireframe! // MARK: PersonsWireframeInput func presentDetailsInterfaceForPerson(person: Person) { // Create the Router for the upcoming module. self.detailsWireframe = DetailsWireframe() // Sends the person data to the next module's Presenter. self.sendPersonToDetailsPresenter( self.detailsWireframe.detailsPresenter, person: person) // Presents the next View. self.detailsWireframe.presentPersonDetailsInterfaceFromViewController(self.personViewController) } // MARK: Private private func sendPersonToDetailsPresenter(detailsPresenter: DetailsPresenter, person: Person) { detailsPresenter.person = person } }
¿Cuando usar la arquitectura VIPER?
Debemos usar VIPER cuando nos encontramos con un proyecto que vaya a ser muy escalable. Además la arquitectura de VIPER ayuda a trabajar más de un desarrollador en la misma app. Gracias a la modularidad podremos encontrar errores más fácilmente, añadir nuevas funcionalidades fácilmente, el código estará más ordenado, habrá menos conflictos con los demás desarrolladores y es más fácil escribir test unitarios.
¿Cuando no usar VIPER?
Puede ser un contra en proyectos pequeños que no tengan futuro de escalabilidad ya que desarrollar la arquitectura gasta más tiempo que una MVC.
Por: Álvaro Royo
iOS Senior Developer | Instructor de «Superpoderes iOS» en el Bootcamp Desarrollo Mobile de KeepCoding