Back home

Patrones de arquitectura de iOS

Patrones arquitectónicos de iOS

##Patrones de arquitectura de iOS Patrones arquitectónicos de iOS

Desmitificando MVC, MVP, MVVM y VIPER Desmitificando MVC, MVP, MVVM y VIPER

¡No te pierdas la Hoja de ruta para desarrolladores de iOS para 2018!

UPD: Diapositivas que presenté en NSLondon disponibles aquí. UPD: Las diapositivas que presenté en NSLondon se pueden encontrar aquí.

¿Te sientes raro mientras haces MVC en iOS? ¿Tienes dudas sobre cambiar a MVVM? ¿Has oído hablar de VIPER, pero no estás seguro de si vale la pena? ¿Te sientes raro al usar MVC en iOS? ¿Tiene preguntas sobre cómo cambiar a MVVM? ¿Has oído hablar de Viper, pero no estás seguro de si vale la pena?

Continúe leyendo y encontrará respuestas a las preguntas anteriores, si no puede quejarse en los comentarios. Continúe leyendo y encontrará las respuestas a las preguntas anteriores y, si no es así, no dude en quejarse en los comentarios.

Estás a punto de estructurar tus conocimientos sobre patrones arquitectónicos en el entorno iOS. Revisaremos brevemente algunos populares y los compararemos en teoría y práctica repasando algunos pequeños ejemplos. Siga los enlaces si necesita más detalles sobre alguno en particular. Desarrollarás tus conocimientos sobre patrones arquitectónicos en el entorno iOS. Revisaremos brevemente algunos ejemplos populares y los compararemos en teoría y práctica. Si necesita información detallada sobre alguno, siga los enlaces.

Dominar los patrones de diseño puede ser adictivo, así que tenga cuidado: podría terminar haciéndose más preguntas ahora que antes de leer este artículo, como estas: Dominar los patrones de diseño puede ser adictivo, así que tenga cuidado: es posible que ahora se esté haciendo más preguntas que antes de leer este artículo, como por ejemplo:

¿Quién se supone que es el propietario de la solicitud de red: un modelo o un controlador? ¿Quién debería ser propietario de las solicitudes de red: modelo o controlador?

¿Cómo paso un modelo a un modelo de vista de una nueva vista? ¿Cómo paso un modelo al modelo de vista de una nueva vista?

¿Quién crea un nuevo módulo VIPER: enrutador o presentador? ¿Quién creó un nuevo módulo Viper: el enrutador o el programa de demostración?

¿Por qué preocuparse por elegir la arquitectura?

¿Por qué debería importarle elegir una arquitectura?

Porque si un día no depuras una clase enorme con docenas de cosas diferentes, no podrás encontrar ni corregir ningún error en tu clase. Naturalmente, es difícil tener en cuenta esta clase como una entidad completa, por lo que siempre te faltarán algunos detalles importantes. Si ya te encuentras en esta situación con tu solicitud, es muy probable que: Porque si no hace esto, un día, al depurar una clase grande con muchas cosas diferentes, no podrá encontrar ni corregir ningún error en la clase. "Por supuesto, es difícil recordar la clase en su totalidad, por lo que siempre te faltan algunos detalles importantes. Si tu aplicación ya se encuentra en esta situación, es probable que:

  • Esta clase es la subclase UIViewController. Esta clase es una subclase de UIViewController.

  • Sus datos almacenados directamente en UIViewController Sus datos se almacenan directamente en UIViewController

  • Tus UIViews no hacen casi nada Tu uiview no hace casi nada

  • El modelo es una estructura de datos tonta. El modelo es una estructura de datos tonta.

  • Tus pruebas unitarias no cubren nada Tus pruebas unitarias no cubren nada.

Y esto puede suceder, incluso a pesar de que estás siguiendo las pautas de Apple e implementando el patrón MVC de Apple, así que no te sientas mal. Hay algún problema con el MVC de Apple, pero volveremos a ello más adelante. Esto puede suceder incluso si sigues las pautas de Apple e implementas el patrón MVC de Apple, así que no te sientas mal. El MVC de Apple tiene algunos problemas, pero volveremos a ello más adelante.

Definamos características de una buena arquitectura: Definamos las características de una buena arquitectura:

  1. Distribución equilibrada de responsabilidades entre entidades con roles estrictos. Distribución equilibrada de responsabilidades entre entidades con roles estrictos.

  2. La capacidad de prueba generalmente proviene de la primera característica (y no se preocupe: es fácil con la arquitectura adecuada). La capacidad de prueba generalmente proviene de la primera característica (no se preocupe: es fácil con la arquitectura adecuada).

  3. Facilidad de uso y bajo coste de mantenimiento. Fácil de usar y bajo coste de mantenimiento.

¿Por qué distribución?

¿Por qué distribuido?

La distribución mantiene una carga considerable en nuestro cerebro mientras intentamos descubrir cómo funcionan las cosas. Si cree que cuanto más se desarrolle, mejor se adaptará su cerebro para comprender la complejidad, entonces tiene razón. Pero esta capacidad no escala linealmente y alcanza el límite muy rápidamente. Entonces, la forma más fácil de vencer la complejidad es dividir las responsabilidades entre múltiples entidades siguiendo el principio de responsabilidad única. La distribución supone una carga considerable para nuestro cerebro cuando intentamos descubrir cómo funcionan las cosas. Si cree que cuanto más se desarrolle, mejor se adaptará su cerebro para comprender la complejidad, está en lo cierto. Pero esta capacidad no crece linealmente y rápidamente alcanzará un límite superior. Por lo tanto, la forma más sencilla de superar la complejidad es dividir la responsabilidad entre múltiples entidades siguiendo el principio de responsabilidad única.

¿Por qué la capacidad de prueba?

¿Razones de comprobabilidad?

Por lo general, esta no es una pregunta para aquellos que ya se sintieron agradecidos por las pruebas unitarias, que fallaron después de agregar nuevas funciones o debido a la refactorización de algunas complejidades de la clase. Esto significa que las pruebas evitaron que esos desarrolladores encontraran problemas en el tiempo de ejecución, lo que podría ocurrir cuando una aplicación está en el dispositivo de un usuario y la solución tarda una semana en llegar al usuario. Por lo general, esto no es un problema para aquellos que ya están agradecidos por las pruebas unitarias que fallan después de agregar una nueva característica, o porque se refactoriza alguna complejidad de la clase. Esto significa que estas pruebas permiten a los desarrolladores evitar descubrir problemas en tiempo de ejecución, lo que puede ocurrir cuando la aplicación está en el dispositivo de un usuario y la solución tarda una semana en llegar al usuario.

¿Por qué facilidad de uso?

¿Por qué facilidad de uso?Esto no requiere respuesta pero cabe mencionar que el mejor código es el código que nunca ha sido escrito. Por lo tanto, cuanto menos código tenga, menos errores tendrá. Esto significa que el deseo de escribir menos código nunca debe explicarse únicamente por la pereza de un desarrollador, y no se debe favorecer una solución más inteligente ignorando su coste de mantenimiento. Esto no requiere una respuesta, pero vale la pena mencionar que el mejor código es el que nunca se ha escrito. Por lo tanto, menos código significa menos errores. Esto significa que el deseo de escribir menos código no debería explicarse únicamente por la pereza de los desarrolladores, y no debería hacerse la vista gorda ante los costes de mantenimiento en favor de soluciones más inteligentes.

Elementos esenciales de MV(X)

Necesidades para MV (X)

Hoy en día tenemos muchas opciones cuando se trata de patrones de diseño arquitectónico: Ahora bien, cuando se trata de patrones de diseño arquitectónico, tenemos muchas opciones:

*MVC *MVP *MVVM *VÍBORA

Los primeros tres suponen colocar las entidades de la aplicación en una de 3 categorías: Los primeros tres supuestos consisten en dividir las entidades de aplicación en tres categorías:

  • Modelos: responsables de los datos del dominio o una capa de acceso a datos que manipula los datos, piense en las clases ‘Persona’ o ‘PersonDataProvider’. Modelo: la capa de acceso a datos responsable de los datos de dominio o de los datos operativos; considere las clases “Persona” o “PersonDataProvider”.

  • Vistas: responsable de la capa de presentación (GUI), para el entorno iOS, piense en todo lo que comience con el prefijo “UI”. Ver: responsable de la capa de presentación (GUI), para entornos iOS, considere todo lo que comience con “UI”.

  • Controlador/Presentador/Modelo de Vista: el pegamento o mediador entre el Modelo y la Vista, en general responsable de alterar el Modelo reaccionando a las acciones del usuario realizadas en la Vista y actualizando la Vista con cambios del Modelo. Controlador/Presentador/Modelo de vista: el pegamento o intermediario entre el modelo y la vista, generalmente responsable de cambiar el modelo en respuesta a las acciones del usuario en la vista y actualizar la vista con cambios del modelo.

Tener entidades divididas nos permite: La división de entidades nos permite:

  • comprenderlos mejor (como ya sabemos) Entenderlos mejor (como ya sabemos)

  • reutilizarlos (principalmente aplicable a la Vista y el Modelo) Reutilizarlos (se aplica principalmente a vistas y modelos)

  • pruébalos de forma independiente pruebas independientes

Comencemos con los patrones MV(X) y volvamos a VIPER más tarde. Comencemos con el modo MV(X) y volvamos a VIPER más tarde.

###MVC

Como solía ser

como era en el pasado

Antes de discutir la visión de Apple sobre MVC, echemos un vistazo a la tradicional. Antes de discutir la visión MVC de Apple, echemos un vistazo al MVC tradicional. TradicionalMVC

En este caso, Ver no tiene estado. Simplemente lo representa el Controlador una vez que se cambia el Modelo. Piense en la página web completamente recargada una vez que presione el enlace para navegar a otro lugar. Aunque es posible implementar el MVC tradicional en una aplicación iOS, no tiene mucho sentido debido al problema arquitectónico: las tres entidades están estrechamente acopladas, cada entidad conoce las otras dos. Esto reduce drásticamente la reutilización de cada uno de ellos; eso no es lo que desea tener en su aplicación. Por esta razón, nos saltamos incluso el intento de escribir un ejemplo MVC canónico. En este caso, la vista no tiene estado. Una vez que se cambia el modelo, el controlador simplemente lo representa. Cuando presiona un enlace para navegar a otro lugar, considere volver a cargar la página web por completo. Si bien es posible implementar MVC tradicional en una aplicación de iOS, esto no tiene mucho sentido debido a problemas de arquitectura: las tres entidades están estrechamente acopladas y cada entidad conoce las otras dos. Esto reduce en gran medida su reutilización, algo que no desea tener en su aplicación. Por esta razón, incluso nos saltamos la escritura de ejemplos canónicos de MVC.

El MVC tradicional no parece ser aplicable al desarrollo de iOS moderno. El MVC tradicional no parece funcionar para el desarrollo de iOS moderno.

MVC de Apple

AppleMVC

Expectativa

Expectativa

CacaoMVC

El Controlador es un mediador entre la Vista y el Modelo para que no se conozcan entre sí. El menos reutilizable es el Controlador y generalmente está bien para nosotros, ya que debemos tener un lugar para toda esa lógica de negocios complicada que no encaja en el Modelo. El controlador es un intermediario entre la vista y el modelo, por lo que no se conocen entre sí. Lo menos reutilizable es el controlador, que normalmente no supone ningún problema para nosotros porque tenemos que proporcionar un lugar para toda la lógica empresarial compleja que no encaja en el modelo.

En teoría, parece muy sencillo, pero sientes que algo anda mal, ¿verdad? Incluso escuchaste a personas abreviar MVC como Massive View Controller. Además, la descarga del controlador de vista se convirtió en un tema importante para los desarrolladores de iOS. ¿Por qué sucede esto si Apple simplemente tomó el MVC tradicional y lo mejoró un poco? En teoría esto parece sencillo, pero sientes que algo anda mal, ¿verdad? Incluso has escuchado a personas abreviar MVC para Large View Controller. Además, la descarga del controlador de visualización se ha convertido en un tema importante para los desarrolladores de iOS. ¿Por qué sucedería esto si Apple simplemente tomó el MVC tradicional y le hizo algunas mejoras?

MVC de Apple

AppleMVC

Realidad

realidad

Cacao realista MVCCocoa MVC lo alienta a escribir controladores de vista masivos, porque están tan involucrados en el ciclo de vida de vista que es difícil decir que están separados. Aunque todavía tiene la capacidad de descargar parte de la lógica de negocios y la transformación de datos al Modelo, no tiene muchas opciones cuando se trata de descargar trabajo a la Vista; en la mayoría de los casos, toda la responsabilidad de la Vista es enviar acciones al Controlador. El controlador de vista termina siendo un delegado y una fuente de datos de todo, y generalmente es responsable de enviar y cancelar las solicitudes de red y… lo que sea. Cocoa MVC lo alienta a escribir una gran cantidad de controladores de vista, porque son tan importantes en el ciclo de vida de la vista que es difícil decir que son independientes. Aunque todavía tiene la capacidad de vender cierta lógica de negocios y transformaciones de datos al modelo, no tiene muchas opciones cuando se trata de descargar el trabajo a la vista; la mayoría de las veces toda la responsabilidad de la vista es enviar acciones al controlador. El controlador de vista es, en última instancia, un delegado y la fuente de datos para todo, y generalmente es responsable de enviar y cancelar solicitudes de red, y… lo que sea.

¿Cuántas veces has visto código como este? ¿Cuántas veces has visto código como este?

  
var userCell = tableView.dequeueReusableCellWithIdentifier("identifier") as UserCell
userCell.configureWithUser(user)

La celda, que es la Vista configurada directamente con el Modelo, por lo que se violan las pautas de MVC, pero esto sucede todo el tiempo y, por lo general, la gente no siente que esté mal. Si sigue estrictamente el MVC, entonces se supone que debe configurar la celda desde el controlador y no pasar el Modelo a la Vista, y esto aumentará el tamaño de su Controlador aún más. La celda es una vista configurada directamente con el modelo, lo que viola las pautas de MVC, pero esto sucede con tanta frecuencia que la gente generalmente no siente que esté mal. Si sigue estrictamente MVC, debe configurar la celda desde el controlador en lugar de pasar el modelo a la vista, lo que aumentará aún más el tamaño del controlador.

Cocoa MVC no se abrevia razonablemente como Massive View Controller. Cocoa MVC está razonablemente simplificado en controladores de vista grandes.

Es posible que el problema no sea evidente hasta que llegue la prueba unitaria (con suerte, sí lo será en su proyecto). Dado que su controlador de vista está estrechamente acoplado con la vista, resulta difícil realizar pruebas porque debe ser muy creativo al burlarse de las vistas y su ciclo de vida, mientras escribe el código del controlador de vista de tal manera que su lógica de negocios esté separada tanto como sea posible del código de diseño de vista. Es posible que este problema no sea obvio antes de la prueba unitaria (con suerte, lo estará en su proyecto). Debido a que su controlador de vista está estrechamente acoplado a las vistas, es difícil realizar pruebas porque al burlarse debe ser muy creativo con las vistas y su ciclo de vida, y al escribir el código del controlador de vista de tal manera que su lógica de negocios esté lo más separada posible del código de diseño de vista.

Echemos un vistazo al ejemplo sencillo del patio de recreo: Veamos un ejemplo sencillo de un parque infantil:

UPD: vea ejemplos de código actualizados de Wasin Thonkaew UPD: vea el ejemplo de código actualizado de Wasin Thonkaew


import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

class GreetingViewController : UIViewController { // View + Controller
    var person: Person!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
    
    func didTapButton(button: UIButton) {
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        self.greetingLabel.text = greeting
        
    }
    // layout code goes here
}
// Assembling of MVC
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
view.person = model;

Ejemplo de MVC

El ensamblaje de MVC se puede realizar en el controlador de vista de presentación El ensamblaje de MVC se puede realizar en el controlador de vista de presentación

Esto no parece muy comprobable, ¿verdad? Podemos mover la generación de saludo a la nueva clase GreetingModel y probarla por separado, pero no podemos probar ninguna lógica de presentación (aunque no hay mucha lógica en el ejemplo anterior) dentro de GreetingViewController sin llamar directamente a los métodos relacionados con UIView (viewDidLoad, didTapButton), lo que podría causar que se carguen todas las vistas, y esto es malo para las pruebas unitarias. Esto no parece muy factible, ¿verdad? Podríamos sustituir Greetings en la nueva clase GreetingModel y probarla individualmente, pero no podríamos probar ninguna lógica de presentación (aunque no hay mucha lógica de este tipo en el ejemplo anterior) en GreetingViewController sin llamar directamente al método relacionado UIView (viewDidLoad didTapButton), lo que podría provocar que se carguen todas las vistas, lo que no favorece las pruebas unitarias.

De hecho, cargar y probar UIViews en un simulador (por ejemplo, iPhone 4S) no garantiza que funcione bien en otros dispositivos (por ejemplo, iPad), por lo que recomendaría eliminar la “Aplicación host” de la configuración de destino de la prueba unitaria y ejecutar las pruebas sin que la aplicación se ejecute en el simulador. De hecho, cargar y probar una UIView en un simulador (como un iPhone 4S) no garantiza que funcionará correctamente en otros dispositivos (como un iPad), por lo que recomiendo eliminar la “Aplicación host” de la configuración de destino de la prueba unitaria y ejecutar las pruebas con la aplicación ejecutándose en el simulador.

Las interacciones entre la Vista y el Controlador no se pueden probar realmente con pruebas unitarias. La interacción entre la vista y el controlador en realidad no se puede probar mediante pruebas unitarias.

Dicho todo esto, podría parecer que Cocoa MVC es un patrón bastante malo para elegir. Pero vamos a evaluarlo en términos de características definidas al principio del artículo: En resumen, elegir Cocoa MVC parece ser un modelo muy malo. Pero vamos a evaluarlo a partir de las características definidas al principio del artículo:

  • **Distribución **: la Vista y el Modelo, de hecho, están separados, pero la Vista y el Controlador están estrechamente acoplados. Distribución: las vistas y los modelos están efectivamente separados, pero las vistas y los controladores están estrechamente acoplados.

  • Probabilidad: debido a la mala distribución, probablemente solo probarás tu modelo. Capacidad de prueba: solo puede probar su modelo debido a una mala distribución.

  • Facilidad de uso: la menor cantidad de código entre otros patrones. Además, todo el mundo está familiarizado con él, por lo que es fácil de mantener incluso para desarrolladores sin experiencia. Facilidad de uso: cantidad mínima de código entre otros modos. Además, todo el mundo está familiarizado con él, por lo que es fácil de mantener incluso para desarrolladores sin experiencia.Cocoa MVC es el patrón de su elección si no está listo para invertir más tiempo en su arquitectura y siente que algo con un mayor costo de mantenimiento es excesivo para su pequeño proyecto favorito. Si no está preparado para invertir más tiempo en la arquitectura y cree que algo con un mayor costo de mantenimiento es redundante para su pequeño proyecto favorito, entonces puede elegir el patrón Cocoa MVC.

Cocoa MVC es el mejor patrón arquitectónico en términos de velocidad de desarrollo. En términos de velocidad de desarrollo, Cocoa MVC es el mejor patrón arquitectónico.

###MVP

Las promesas de Cocoa MVC cumplidas

La promesa de Cocoa MVC se ha cumplido

Variante de vista pasiva de MVP

¿No se parece exactamente al MVC de Apple? Sí, lo hace, y su nombre es MVP (variante de vista pasiva). Pero espera un momento… ¿Significa esto que el MVC de Apple es en realidad un MVP? No, no lo es, porque si recuerda, allí, la Vista está estrechamente acoplada con el Controlador, mientras que el mediador del MVP, Presenter, no tiene nada que ver con el ciclo de vida del controlador de vista, y la Vista se puede burlar fácilmente, por lo que no hay ningún código de diseño en el Presentador, pero es responsable de actualizar la Vista con datos y estado. ¿No se parece al MVC de Apple? Sí, lo es, y su nombre es MVP (Passive View Variant). Pero espera, ¿significa esto que el MVC de Apple es en realidad MVP? No, no lo es, porque si recuerda, allí la vista está estrechamente acoplada al controlador, y el mediador del MVP, el presentador, no tiene nada que ver con el ciclo de vida del controlador de vista, la vista se puede burlar fácilmente, por lo que no hay código de diseño en el presentador, pero es responsable de actualizar la vista con datos y estado.

¿Y si te dijera que UIViewController es la Vista? Si te digo que UIViewController es una vista.

En términos de MVP, las subclases de UIViewController son de hecho Vistas y no Presentadores. Esta distinción proporciona una excelente capacidad de prueba, lo que tiene un coste en la velocidad de desarrollo, porque hay que vincular manualmente los datos y los eventos, como se puede ver en el ejemplo: En lo que respecta a MVP, las subclases de UIViewController son en realidad vistas en lugar de presentadores. Esta distinción proporciona una excelente capacidad de prueba, pero tiene un costo de velocidad de desarrollo porque hay que realizar datos manuales y vinculación de eventos, como se puede ver en el ejemplo:


import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

protocol GreetingView: class {
    func setGreeting(greeting: String)
}

protocol GreetingViewPresenter {
    init(view: GreetingView, person: Person)
    func showGreeting()
}

class GreetingPresenter : GreetingViewPresenter {
    unowned let view: GreetingView
    let person: Person
    required init(view: GreetingView, person: Person) {
        self.view = view
        self.person = person
    }
    func showGreeting() {
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        self.view.setGreeting(greeting)
    }
}

class GreetingViewController : UIViewController, GreetingView {
    var presenter: GreetingViewPresenter!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
    
    func didTapButton(button: UIButton) {
        self.presenter.showGreeting()
    }
    
    func setGreeting(greeting: String) {
        self.greetingLabel.text = greeting
    }
    
    // layout code goes here
}
// Assembling of MVP
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
let presenter = GreetingPresenter(view: view, person: model)
view.presenter = presenter

Ejemplo de MVP

Nota importante sobre el montaje

Notas importantes sobre el montaje

El MVP es el primer patrón que revela el problema de ensamblaje que se produce al tener tres capas realmente separadas. Como no queremos que Vista sepa sobre el Modelo, no es correcto realizar el ensamblaje en el controlador de vista de presentación (que es Vista), por lo que tenemos que hacerlo en otro lugar. Por ejemplo, podemos crear el servicio Router para toda la aplicación, que será responsable de realizar el ensamblaje y la presentación Vista a Vista. Este problema surge y debe abordarse no solo en el MVP sino también en todos los patrones siguientes. MVP es la primera muestra que revela problemas de ensamblaje debido a que tiene tres capas realmente independientes. Como no queremos que la vista sepa sobre el modelo, es incorrecto realizar el ensamblaje cuando se presenta el controlador de vista (es decir, la vista), por lo que tenemos que hacerlo en otro lugar. Por ejemplo, podemos crear un servicio de enrutador para toda la aplicación que será responsable de realizar el ensamblaje y la presentación vista a vista. Este problema no sólo aparece en MVP, sino que debe resolverse en todos los patrones siguientes.

Veamos las características del MVP: Echemos un vistazo a las características de MVP:

  • Distribución: tenemos la mayor parte de las responsabilidades divididas entre el Presentador y el Modelo, con una Vista bastante tonta (en el ejemplo anterior, el Modelo también es tonto). Distribución: distribuimos la mayoría de las responsabilidades entre el presentador y el modelo, y utilizamos vistas bastante estúpidas (en el ejemplo anterior, el modelo también era estúpido).

  • Probabilidad: es excelente, podemos probar la mayor parte de la lógica empresarial gracias a la vista tonta. Capacidad de prueba: muy buena, podemos probar la mayor parte de la lógica empresarial gracias a vistas tontas.

  • Fácil de usar— En nuestro ejemplo irrealmente simple, la cantidad de código se duplica en comparación con el MVC, pero al mismo tiempo, la idea del MVP es muy clara. Facilidad de uso: en nuestro ejemplo irrealmente simple, la cantidad de código se duplica en comparación con MVC, pero al mismo tiempo, el concepto de MVP es muy claro.

MVP en iOS significa una excelente capacidad de prueba y mucho código. MVP en iOS significa una gran capacidad de prueba y mucho código.

###MVP jugador más valioso

Con ataduras y sirenas

con cuerda y sombrero

Existe otra versión del MVP: el MVP del controlador supervisor. Esta variante incluye el enlace directo de Vista y Modelo mientras que el Presentador (el controlador supervisor) aún maneja las acciones de la Vista y es capaz de cambiar la Vista. Hay otro tipo de MVP: el MVP del jugador supervisor. Esta variante incluye la vinculación directa de la vista y el modelo, mientras que el presentador (controlador de monitoreo) aún maneja las operaciones desde la vista y puede cambiar la vista.

Variante del presentador supervisor del MVP

Pero como ya hemos aprendido antes, una vaga separación de responsabilidades es mala, así como un estrecho acoplamiento entre Vista y Modelo. Esto es similar a cómo funcionan las cosas en el desarrollo de escritorio Cocoa. Pero como hemos aprendido antes, una vaga separación de responsabilidades es mala, al igual que el estrecho acoplamiento entre la vista y el modelo. Esto es similar a cómo funciona en el desarrollo de escritorio Cocoa.

Al igual que con el MVC tradicional, no veo sentido en escribir un ejemplo para la arquitectura defectuosa. Al igual que con el MVC tradicional, no creo que sea necesario escribir ejemplos para una arquitectura defectuosa.

###MVVM

Lo último y lo mejor del tipo MV(X) La última y mejor categoría MV(X)El MVVM es el más nuevo del tipo MV(X), por lo tanto, esperemos que haya surgido teniendo en cuenta los problemas que MV(X) enfrentaba anteriormente. MVVM es el último tipo de MV(X), así que esperemos que surja considerando los problemas que MV(X) enfrentó antes.

En teoría, Model-View-ViewModel se ve muy bien. La Vista y el Modelo ya nos son familiares, pero también el Mediador, representado como el Ver Modelo. En teoría, modelo-vista-modelo se ve muy bien. Las vistas y los modelos ya nos son familiares, al igual que las mediaciones, representadas como modelos de vista.

Es bastante similar al MVP: Esto es muy similar a MVP:

  • MVVM trata el controlador de vista como Ver MVVM trata a los controladores de vista como vistas

  • No existe un acoplamiento estrecho entre la Vista y el Modelo No existe un acoplamiento estrecho entre la vista y el modelo.

Además, es vinculante como la versión supervisora del MVP; sin embargo, esta vez no entre Vista y Modelo, sino entre Vista y Ver modelo. Además, se vincula como la versión regulatoria de MVP; sin embargo, esta vez no es entre la vista y el modelo, sino entre la vista y el modelo de vista.

Entonces, ¿qué es Ver modelo en la realidad de iOS? Básicamente es una representación independiente de UIKit de su Vista y su estado. Ver modelo invoca cambios en el Modelo y se actualiza con el Modelo actualizado, y dado que tenemos un enlace entre Ver y Ver modelo, el primero se actualiza en consecuencia. ¿Qué es un modelo de vista en iOS? Básicamente es la representación independiente de UIKit de una vista y su estado. El modelo de vista llama a los cambios en el modelo y se actualiza con el modelo actualizado, y dado que tenemos un vínculo entre la vista y el modelo de vista, el primer modelo se actualizará en consecuencia.

Enlaces

vinculante

Los mencioné brevemente en la parte de MVP, pero analicémoslos un poco aquí. Los enlaces vienen listos para el desarrollo de OS X, pero no los tenemos en la caja de herramientas de iOS. Por supuesto, tenemos KVO y notificaciones, pero no son tan convenientes como los enlaces. Los mencioné brevemente en la sección MVP, pero analicémoslos aquí. Los enlaces vienen listos para usar para el desarrollo de OS X, pero no los tenemos en la caja de herramientas de iOS. Por supuesto, tenemos KVO y notificaciones, pero no son tan convenientes como vinculantes.

Así que, siempre que no queramos escribirlos nosotros mismos, tenemos dos opciones: Entonces, si no queremos escribirlo nosotros mismos, tenemos dos opciones:

  • Una de las bibliotecas de enlaces basadas en KVO como RZDataBinding o SwiftBond Una de las bibliotecas de enlaces basadas en KVO, como RZDataBinding o SwiftBond

  • Las bestias de programación reactiva funcional a gran escala como ReactiveCocoa, RxSwift o PromiseKit. Herramientas de programación reactiva con todas las funciones como ReactiveCocoa, RxSwift o promesa eKit.

De hecho, hoy en día, si escuchas “MVVM”, piensas en ReactiveCocoa y viceversa. Aunque es posible construir el MVVM con enlaces simples, ReactiveCocoa (o sus hermanos) le permitirán obtener la mayor parte del MVVM. De hecho, hoy en día, si escuchas “MVVM”, piensas en ReactiveCocoa y viceversa. Si bien es posible construir MVVM con enlaces simples, ReactiveCocoa (o un hermano) le permitirá obtener la mayor parte de MVVM.

Hay una amarga verdad acerca de los marcos reactivos: el gran poder conlleva una gran responsabilidad. Es muy fácil estropear las cosas cuando te vuelves reactivo. En otras palabras, si hace algo mal, es posible que dedique mucho tiempo a depurar la aplicación, así que eche un vistazo a esta pila de llamadas. Hay una verdad dolorosa acerca de los marcos reactivos: un gran poder conlleva una gran responsabilidad. Es fácil equivocarse cuando reacciona de forma exagerada. En otras palabras, si hace algo mal, puede pasar mucho tiempo depurando su aplicación, así que mire esta pila de llamadas.

Depuración reactiva

En nuestro ejemplo simple, el marco FRF o incluso el KVO es excesivo; en su lugar, le pediremos explícitamente al modelo de vista que se actualice usando el método showGreeting y usaremos la propiedad simple para la función de devolución de llamada GreetingDidChange. En nuestro ejemplo simple, el marco FRF o incluso KVO es redundante; en su lugar, le pediremos explícitamente al modelo de vista que se actualice usando el método showGreeting y una propiedad simple usando la función de devolución de llamada GreetingDidChange.


import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

protocol GreetingViewModelProtocol: class {
    var greeting: String? { get }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set } // function to call when greeting did change
    init(person: Person)
    func showGreeting()
}

class GreetingViewModel : GreetingViewModelProtocol {
    let person: Person
    var greeting: String? {
        didSet {
            self.greetingDidChange?(self)
        }
    }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())?
    required init(person: Person) {
        self.person = person
    }
    func showGreeting() {
        self.greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
    }
}

class GreetingViewController : UIViewController {
    var viewModel: GreetingViewModelProtocol! {
        didSet {
            self.viewModel.greetingDidChange = { [unowned self] viewModel in
                self.greetingLabel.text = viewModel.greeting
            }
        }
    }
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self.viewModel, action: "showGreeting", forControlEvents: .TouchUpInside)
    }
    // layout code goes here
}
// Assembling of MVVM
let model = Person(firstName: "David", lastName: "Blaine")
let viewModel = GreetingViewModel(person: model)
let view = GreetingViewController()
view.viewModel = viewModel

Ejemplo de MVVM

Y volvamos nuevamente a nuestra evaluación de características: Volver a nuestra evaluación de funciones:

  • Distribución: no está claro en nuestro pequeño ejemplo, pero, de hecho, la Vista de MVVM tiene más responsabilidades que la Vista de MVP. Porque el primero actualiza su estado desde el modelo de vista configurando enlaces, mientras que el segundo simplemente reenvía todos los eventos al presentador y no se actualiza. Distribución: no está claro en nuestro pequeño ejemplo, pero de hecho, la vista de MVVM tiene más responsabilidades que la vista de MVP. Porque el primero actualiza el estado en el modelo de vista configurando el enlace, mientras que el segundo simplemente reenvía todos los eventos al presentador sin actualizarse.

  • Probabilidad: el modelo de vista no sabe nada sobre la vista, esto nos permite probarlo fácilmente. Es posible que la Vista también se pruebe, pero como depende de UIKit, es posible que desee omitirla. Capacidad de prueba: el modelo de vista no sabe nada sobre la vista, lo que nos permite probarla fácilmente. También se pueden probar las vistas, pero como dependen de UIKit, es posible que desees omitirlas.

  • Fácil de usar: tiene la misma cantidad de código que el MVP en nuestro ejemplo, pero en la aplicación real donde tendrías que reenviar todos los eventos desde la Vista al Presentador y actualizar la Vista manualmente, MVVM sería mucho más delgado si usaras enlaces. Fácil de usar: tiene la misma cantidad de código que MVP en nuestro ejemplo, pero en una aplicación real tendría que reenviar todos los eventos desde la vista al presentador y actualizar la vista manualmente, MVVM sería más delgado si se usaran enlaces.> MVVM es muy atractivo, ya que combina los beneficios de los enfoques antes mencionados y, además, no requiere código adicional para las actualizaciones de View debido a los enlaces en el lado de View. No obstante, la capacidad de prueba todavía se encuentra en un buen nivel. MVVM es muy atractivo porque combina las ventajas de los métodos anteriores y, debido al enlace en el lado de la vista, no requiere código adicional para actualizar la vista. Aún así, la capacidad de prueba está en un buen nivel.

VÍBORA

Experiencia de construcción LEGO transferida al diseño de la aplicación iOS La experiencia de construcción LEGO transferida al diseño de aplicaciones iOS

VIPER es nuestro último candidato, lo cual es particularmente interesante porque no proviene de la categoría MV(X). VIPER es nuestro último candidato, lo cual es muy interesante porque no es de clase MV(X).

A estas alturas, debe estar de acuerdo en que la granularidad de las responsabilidades es muy buena. VIPER hace otra iteración sobre la idea de separar responsabilidades, y esta vez tenemos cinco capas. A estas alturas hay que estar de acuerdo en que la granularidad de las responsabilidades es bastante buena. VIPER tiene otra iteración del concepto de separación de responsabilidades, esta vez tenemos 5 capas.

VÍBORA

  • Interactor: contiene lógica empresarial relacionada con los datos (entidades) o las redes, como crear nuevas instancias de entidades o recuperarlas del servidor. Para esos fines, utilizará algunos Servicios y Administradores que no se consideran parte del módulo VIPER sino más bien una dependencia externa. Interactor: contiene lógica empresarial relacionada con datos (entidades) o redes, como la creación de nuevas instancias de entidades o su recuperación del servidor. Para estos fines utilizará algunos servicios y gestores que no se consideran parte del módulo VIPER sino una dependencia externa.

  • Presentador significa: contiene la lógica empresarial relacionada con la interfaz de usuario (pero independiente de UIKit), invoca métodos en el Interactor. Presentador: contiene lógica empresarial relacionada con la interfaz de usuario (pero independiente de UIKit) y llama a métodos en el interactor.

  • Entidades significa: sus objetos de datos simples, no la capa de acceso a datos, porque eso es responsabilidad del interactor. Entidades: sus objetos de datos normales, no la capa de acceso a datos, ya que es responsabilidad del interactuante.

  • Enrutador: responsable de las conexiones entre los módulos VIPER. Enrutador: responsable de la transición entre los módulos Viper.

Básicamente, el módulo VIPER puede ser una pantalla o toda la historia de usuario de su aplicación; piense en la autenticación, que puede ser una pantalla o varias relacionadas. ¿Qué tan pequeños deben ser tus bloques “LEGO”? - Tu decides. Básicamente, un módulo VIPER puede ser una pantalla o la historia completa del usuario de la aplicación; piense en la autenticación, puede ser una pantalla o varias pantallas relacionadas. ¿Qué tan pequeños deben ser tus ladrillos “LEGO”? - Tu decides.

Si lo comparamos con el tipo MV(X), veremos algunas diferencias en la distribución de responsabilidades: Si comparamos esto con el tipo MV(X) veremos algunas diferencias en la distribución de responsabilidades:

  • La lógica del modelo (interacción de datos) se trasladó al interactor con las entidades como estructuras de datos tontas. La lógica del modelo (interacción de datos) se convierte en estructuras de datos tontas que interactúan con entidades.

  • Solo las funciones de representación de la interfaz de usuario del Controlador/Presentador/ViewModel se trasladaron al Presentador, pero no las capacidades de alteración de datos. Solo se transfiere a la presentación la funcionalidad de presentación de la interfaz de usuario del controlador/presentador/modelo de vista, no la funcionalidad de modificación de datos.

  • VIPER es el primer patrón que aborda explícitamente la responsabilidad de navegación, que se supone debe ser resuelta por el enrutador. VIPER es el primer patrón que maneja explícitamente las responsabilidades de navegación, que deben ser manejadas por el enrutador.

La forma adecuada de realizar el enrutamiento es un desafío para las aplicaciones de iOS; los patrones MV(X) simplemente no abordan este problema. El enrutamiento correcto es un desafío para las aplicaciones de iOS y el patrón MV(X) simplemente no puede resolver este problema.

El ejemplo no cubre enrutamiento o interacción entre módulos, ya que esos temas no están cubiertos en absoluto por los patrones MV(X). Este ejemplo no implica enrutamiento ni interacciones entre módulos, ya que el patrón MV(X) no cubre estos temas en absoluto.


import UIKit

struct Person { // Entity (usually more complex e.g. NSManagedObject)
    let firstName: String
    let lastName: String
}

struct GreetingData { // Transport data structure (not Entity)
    let greeting: String
    let subject: String
}

protocol GreetingProvider {
    func provideGreetingData()
}

protocol GreetingOutput: class {
    func receiveGreetingData(greetingData: GreetingData)
}

class GreetingInteractor : GreetingProvider {
    weak var output: GreetingOutput!
    
    func provideGreetingData() {
        let person = Person(firstName: "David", lastName: "Blaine") // usually comes from data access layer
        let subject = person.firstName + " " + person.lastName
        let greeting = GreetingData(greeting: "Hello", subject: subject)
        self.output.receiveGreetingData(greeting)
    }
}

protocol GreetingViewEventHandler {
    func didTapShowGreetingButton()
}

protocol GreetingView: class {
    func setGreeting(greeting: String)
}

class GreetingPresenter : GreetingOutput, GreetingViewEventHandler {
    weak var view: GreetingView!
    var greetingProvider: GreetingProvider!
    
    func didTapShowGreetingButton() {
        self.greetingProvider.provideGreetingData()
    }
    
    func receiveGreetingData(greetingData: GreetingData) {
        let greeting = greetingData.greeting + " " + greetingData.subject
        self.view.setGreeting(greeting)
    }
}

class GreetingViewController : UIViewController, GreetingView {
    var eventHandler: GreetingViewEventHandler!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
    
    func didTapButton(button: UIButton) {
        self.eventHandler.didTapShowGreetingButton()
    }
    
    func setGreeting(greeting: String) {
        self.greetingLabel.text = greeting
    }
    
    // layout code goes here
}
// Assembling of VIPER module, without Router
let view = GreetingViewController()
let presenter = GreetingPresenter()
let interactor = GreetingInteractor()
view.eventHandler = presenter
presenter.view = view
presenter.greetingProvider = interactor
interactor.output = presenter

Una vez más, volvamos a las características: Volver a las características nuevamente:

  • Distribución: Sin lugar a dudas, VIPER es un campeón en la distribución de responsabilidades. Distribución: Sin duda, Viper es un campeón en la distribución de responsabilidades.

  • Probabilidad: no hay sorpresas aquí, mejor distribución, mejor capacidad de prueba. Capacidad de prueba: aquí no hay sorpresas, mejor distribución: mejor capacidad de prueba.

  • Fácil de usar — Finalmente, dos de los anteriores tienen un costo de mantenimiento, como ya habrás adivinado. Tienes que escribir una gran cantidad de interfaces para clases con responsabilidades muy pequeñas. Facilidad de uso: finalmente, como habrás adivinado, los dos elementos mencionados anteriormente son costos de mantenimiento. Tienes que escribir muchas interfaces para clases con responsabilidades muy pequeñas.

Entonces, ¿qué pasa con LEGO?

¿Qué pasa con Lego?Mientras usas VIPER, es posible que tengas ganas de construir el Empire State Building con bloques LEGO, y eso es una señal de que tienes un problema. Quizás sea demasiado pronto para adoptar VIPER para su aplicación y debería considerar algo más simple. Algunas personas ignoran esto y continúan disparando con sus cañones a los gorriones. Supongo que creen que sus aplicaciones se beneficiarán de VIPER al menos en el futuro, incluso si ahora el costo de mantenimiento es excesivamente alto. Si crees lo mismo, te recomiendo que pruebes Generamba, una herramienta para generar esqueletos VIPER. Aunque para mí personalmente es como usar un sistema de puntería automatizado para el cañón en lugar de simplemente disparar con una honda. Al utilizar VIPER, es posible que sienta que está construyendo el Empire State Building con Legos, lo cual es una señal de que tiene un problema. Quizás sea demasiado pronto para adoptar VIPER para su aplicación y debería considerar algunos métodos más simples. Algunas personas ignoraron esto y continuaron disparando a los gorriones con sus cañones. Supongo que creen que su aplicación se beneficiará de VIPER al menos en el futuro, incluso si los costos de mantenimiento son excesivamente altos ahora. Si lo crees así, te sugiero que pruebes Generamba, una herramienta para generar el esqueleto de una serpiente venenosa. Aunque, personalmente, para mí es más como disparar con un sistema de puntería automática que con una simple resortera.

###Conclusión Conclusión

Revisamos varios patrones arquitectónicos y espero que hayas encontrado algunas respuestas a lo que te molestaba, pero no tengo ninguna duda de que te diste cuenta de que no existe una fórmula mágica, por lo que elegir un patrón arquitectónico es una cuestión de sopesar las compensaciones en tu situación particular. Hemos discutido varios patrones arquitectónicos y espero que haya encontrado algunas respuestas a las preguntas que lo han estado molestando, pero estoy seguro de que se ha dado cuenta de que no existe una solución mágica, por lo que elegir un patrón arquitectónico es una cuestión de sopesar los pros y los contras en su situación específica.

Por tanto, es natural tener una combinación de arquitecturas en una misma aplicación. Por ejemplo: comenzó con MVC, luego se dio cuenta de que una pantalla en particular se volvió demasiado difícil de mantener de manera eficiente con MVC y cambió a MVVM, pero solo para esta pantalla en particular. No hay necesidad de refactorizar otras pantallas para las cuales MVC realmente funciona bien, porque ambas arquitecturas son fácilmente compatibles. Por tanto, es natural mezclar arquitecturas en una misma aplicación. Por ejemplo: comienzas con MVC, luego te das cuenta de que es difícil mantener una pantalla específica de manera eficiente usando MVC, así que cambia a MVVM, pero solo para esta pantalla específica. No es necesario refactorizar otras pantallas en las que MVC realmente funcione bien, ya que ambas arquitecturas son fácilmente compatibles.

Haz que todo sea lo más simple posible, pero no más simple: Albert Einstein Haz que todo sea lo más simple posible, pero no tan simple como sea posible - Albert Einstein

¡Gracias por leer! Si te gustó este artículo, aplaude para que otras personas también puedan leerlo :) ¡Gracias por leer! Si te gustó esta publicación, aplaude para que otros puedan leerla también.

Sígueme en Twitter para obtener más diseños y patrones de iOS. Sígueme en Twitter para obtener más diseños y patrones de iOS.

Texto original