Arquitectura Viper: principales ventajas

Autor: | Última modificación: 2 de abril de 2024 | Tiempo de Lectura: 4 minutos
Temas en este post:

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()
        }
    }
}

INVITACIÓN bootcamp mobile (sesión informativa) - Arquitectura VIPER

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

Álvaro Royo

iOS Senior Developer | Instructor de «Superpoderes iOS» en el Bootcamp Desarrollo Mobile de KeepCoding