tutorial mvc example ios architecture software-design viper-architecture

ios - mvc - viper swift 4



Preguntas sobre VIPER-Clean Architecture (3)

He estado leyendo acerca de Clean Architecture de Robert Martin y más específicamente sobre VIPER .

Luego me encontré con este artículo / publicación Brigade''s Experience Using a MVC Alternative que describe más o menos lo que estoy haciendo actualmente.

Después de tratar de implementar VIPER en un nuevo proyecto de iOS, me encontré con algunas preguntas:

  • ¿Está bien que el presentador solicite información en la vista o la "información que pasa" siempre comienza desde la vista? Por ejemplo, si la vista desencadenó alguna acción en el presentador, pero luego, dependiendo de los parámetros pasados ​​a través de esa acción, el presentador podría necesitar más información. Lo que quiero decir es: el usuario tocó "doneWithState:", si estado == "algo", obtener información de la vista para crear una entidad, si estado == "algo más", animar algo en la vista. ¿Cómo debo manejar este tipo de escenario?
  • Digamos que un "módulo" (grupo de componentes VIPER) decide presentar otro módulo modalmente. ¿Quién debería ser responsable de decidir si el segundo módulo se presentará de forma modal, el primer wireframe del módulo o el wireframe del segundo módulo?
  • Además, digamos que la vista del segundo módulo se inserta en un controlador de navegación, ¿cómo se debe manejar la acción "hacia atrás"? ¿Debo configurar manualmente un botón "Atrás" con una acción en el controlador de vista del segundo módulo, que llama al presentador, que llama al wireframe del segundo módulo que descarta y le dice al primer módulo que se descartó para que el controlador de vista del primer módulo pueda ¿Quieres mostrar algo?
  • ¿Deberían los diferentes módulos hablar solo a través de la estructura de alambre o también a través de delegados entre presentadores? Por ejemplo, si la aplicación navegó a un módulo diferente, pero después de eso, el usuario presionó "cancelar" o "guardar" y esa opción debe retroceder y cambiar algo en el primer módulo (tal vez mostrar una animación que se guardó o eliminar algo )
  • Digamos que se seleccionó un pin en un mapa, que se muestra PinEditViewController. Al retroceder, es posible que el color del pin seleccionado deba cambiar según las acciones de uso en PinEditViewController. ¿Quién debería mantener el estado del pin seleccionado actual, MapViewController, MapPresenter o MapWireframe para que yo sepa, al volver, qué pin debería cambiar de color?

1. Puede el presentador consultar información de la vista

Para responder a esto a su satisfacción, necesitamos más detalles sobre el caso particular. ¿Por qué la vista no puede proporcionar más información de contexto directamente a la devolución de llamada?

Le sugiero que pase el Presentador a un objeto Command para que el Presentador no tenga que saber qué hacer, en cuyo caso. El presentador puede ejecutar el método del objeto, pasando cierta información por sí mismo si es necesario, sin saber nada sobre el estado de la vista (y por lo tanto introduciendo un alto acoplamiento a ella).

  • La vista está en un estado que llama x (opuesto a y y z ). De todos modos, conoce su estado.
  • El usuario termina la acción. Ver informa a su delegado (presentador) acerca de haber terminado. Debido a que está tan involucrado, construye un Objeto de Transferencia de Datos para contener toda la información usual. Uno de los atributos de este DTO es un id<FollowUpCommand> followUpCommand . View crea un XFollowUpCommand (opuesto a YFollowUpCommand y ZFollowUpCommand ) y establece sus parámetros en consecuencia, luego lo coloca en el DTO.
  • Presentador recibe la llamada al método. Hace algo con los datos sin importar qué concreto FollowUpCommand esté allí. Luego ejecuta el único método del protocolo, followUpCommand.followUp . La implementación concreta sabrá qué hacer.

Si tiene que hacer un switch-case / if-else en alguna propiedad, la mayoría de las veces ayudaría a modelar las opciones como objetos que heredan de un protocolo común y pasar los objetos en lugar del estado.

2. Módulo modal

¿Debería el módulo de presentación o el módulo presentado decidir si es modal? - El módulo presentado (el segundo) debe decidir siempre que esté diseñado para ser utilizado de manera modal solamente. Ponga conocimiento sobre una cosa en la cosa misma. Si su modo de presentación depende del contexto, bueno, entonces el módulo en sí no puede decidir.

El wireframe del segundo módulo recibirá un mensaje como este:

[secondWireframe presentYourStuffIn:self.viewController]

El parámetro es el objeto para el que debe tener lugar la presentación. También puede pasar un parámetro asModal , si el módulo está diseñado para usarse de las dos maneras. Si solo hay una forma de hacerlo, coloque esta información en el módulo afectado (el que se presenta).

Luego hará algo como:

- (void)presentYourStuffIn:(UIViewController)viewController { // set up module2ViewController [self.presenter configureUserInterfaceForPresentation:module2ViewController]; // Assuming the modal transition is set up in your Storyboard [viewController presentViewController:module2ViewController animated:YES completion:nil]; self.presentingViewController = viewController; }

Si usas Segmentos del Guión Gráfico, deberás hacer las cosas de forma un poco diferente.

3. Jerarquía de navegación

Además, digamos que la vista del segundo módulo se inserta en un controlador de navegación, ¿cómo se debe manejar la acción "hacia atrás"?

Si va a "todos los VIPER", sí, tiene que pasar de la vista a su estructura alámbrica y pasar a otra estructura alámbrica.

Para pasar datos del módulo presentado ("Segundo") al módulo de presentación ("Primero"), agregue SecondDelegate e SecondDelegate en FirstPresenter . Antes de que SecondDelegate el módulo presentado, envía un mensaje a SecondDelegate para notificar sobre el resultado.

"No pelear contra el marco", sin embargo. Tal vez pueda aprovechar algunas de las sutilezas del controlador de navegación al sacrificar la pureza de VIPER. Los segmentos ya son un paso en la dirección de un mecanismo de enrutamiento. Mire VTDAddWireframe para los métodos UIViewControllerTransitioningDelegate en un wireframe que introduce animaciones personalizadas. Tal vez esto sea de ayuda:

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed { return [[VTDAddDismissalTransition alloc] init]; } - (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { return [[VTDAddPresentationTransition alloc] init]; }

Primero pensé que necesitarías mantener una pila de wireframes similar a la pila de navegación, y que todos los wireframes "activos" del módulo están vinculados entre sí. Pero este no es el caso. Los wireframes gestionan los contenidos del módulo, pero la pila de navegación es la única pila en su lugar que representa qué controlador de vista es visible.

4. Flujos de mensajes

¿Deberían los diferentes módulos hablar solo a través de la estructura de alambre o también a través de delegados entre presentadores?

Si envía directamente el objeto de otro módulo B un mensaje del presentador A, ¿qué sucederá entonces?

Como la vista del receptor no está visible, una animación no puede comenzar, por ejemplo. El presentador aún tiene que esperar el Wireframe / Router. Por lo tanto, tiene que poner en cola la animación hasta que se vuelva a activar. Esto hace que el Presentador tenga más estado, lo que hace que sea más difícil trabajar con él.

En cuanto a la arquitectura, piense en el papel que desempeñan los módulos. En la arquitectura Puertos / Adaptadores, de la cual la Arquitectura Limpia oculta algunos conceptos, el problema es más evidente. Como una analogía: una computadora tiene muchos puertos. El puerto USB no se puede comunicar con el puerto LAN. Cada flujo de información debe enrutarse a través del núcleo.

¿Cuál es el núcleo de tu aplicación?

¿Tienes un Modelo de Dominio? ¿Tiene un conjunto de servicios que se consultan desde varios módulos? Los módulos VIPER se centran en la vista. Los módulos de cosas que comparten, como los mecanismos de acceso a datos, no pertenecen a un módulo en particular. Eso es lo que puedes llamar el núcleo. Allí, debes realizar cambios en los datos. Si otro módulo se vuelve visible, capta los datos modificados.

Sin embargo, solo para fines de animación, deje que el enrutador sepa qué hacer y emita un comando al presentador, según el cambio del módulo.

En el código de muestra de VIPER Todo:

  • La "Lista" es la vista de la raíz.
  • Se muestra una vista "Agregar" en la parte superior de la vista de lista.
  • ListPresenter implementa AddModuleDelegate. Si el módulo "Agregar" finaliza, ListPresenter lo sabrá, no su estructura alámbrica porque la vista ya está en la pila de navegación .

5. Mantener el estado

¿Quién debería mantener el estado del pin seleccionado actual, MapViewController, MapPresenter o MapWireframe para que yo sepa, al volver, qué pin debería cambiar de color?

Ninguna. Evite la condición de estado en los servicios de su módulo de visualización para reducir el costo de mantenimiento de su código. En cambio, intente averiguar si puede pasar una representación de los cambios de pin durante los cambios.

Intente llegar a las Entidades para obtener el estado (a través de Presentador e Interactor y otras cosas).

Esto no significa que crees un objeto Pin en tu capa de vista, lo pases del controlador de vista para ver el controlador, cambies sus propiedades y luego lo envíes de vuelta para reflejar los cambios. ¿Sería un NSDictionary con cambios serializados? Puede poner el nuevo color y enviarlo desde PinEditViewController a su Presentador, que emite un cambio en MapViewController .

Ahora hice trampa: MapViewController necesita tener estado. Necesita saber todos los pines. Luego le sugerí que pasara un diccionario de cambios para que MapViewController sepa qué hacer.

¿Pero cómo identificas el pin afectado?

Cada pin puede tener su propia identificación. Tal vez esta ID es solo su ubicación en el mapa. Tal vez es su índice en una matriz de pin. Necesitas algún tipo de identificador en cualquier caso. O bien, crea un objeto envoltorio identificable que se aferra a un alfiler durante la operación. (Eso suena demasiado ridículo con el propósito de cambiar el color, sin embargo).

Envío de eventos para cambiar el estado

VIPER está muy basado en el Servicio. Hay muchos objetos en su mayoría apátridas unidos para pasar mensajes y transformar datos. En la publicación de Brigade Engineering, también se muestra un enfoque centrado en los datos.

Las entidades están en una capa bastante delgada. En el lado opuesto al espectro que tengo en mente, se encuentra un Modelo de Dominio . Este patrón no es necesario para cada aplicación. Sin embargo, modelar el núcleo de su aplicación de manera similar puede ser beneficioso para responder algunas de sus preguntas.

A diferencia de las entidades como contenedores de datos a los que todos pueden llegar a través de "administradores de datos", un dominio protege sus entidades. Un dominio también informará sobre los cambios de forma proactiva. (A través de NSNotificationCenter , para empezar, menos a través de llamadas de mensajes directos similares a comandos).

Ahora esto también podría ser adecuado para tu estuche Pin:

  • PinEditViewController cambia el color del pin. Esto es un cambio en un componente de UI.
  • El cambio en el componente UI corresponde a un cambio en su modelo subyacente. Realiza los cambios a través de la pila del módulo VIPER. (¿Persisten los colores? Si no, la entidad Pin siempre es efímera, pero sigue siendo una entidad porque su identidad importa, no solo sus valores).
  • El Pin correspondiente ha cambiado de color y publica una notificación a través de NSNotificationCenter .
  • Por casualidad (es decir, Pin no lo sabe), algún interaccionista se suscribe a estas notificaciones y cambia la apariencia de su vista.

Aunque esto también podría funcionar para tu caso, creo que vincular la edición


Esta respuesta puede no estar relacionada, pero la estoy poniendo aquí como referencia. El sitio Clean Swift es una implementación excelente de la " Arquitectura limpia " del tío Bob en poco tiempo. El propietario lo llama VIP (aún contiene las "Entidades" y el Enrutador / estructura de alambre).

El sitio te da plantillas XCode. Entonces digamos que quieres crear una nueva escena (él llama módulos VIPER, "escenas"), todo lo que haces es File-> new-> sceneTemplate.

Esta plantilla crea un lote de 7 archivos que contienen todos los dolores de cabeza del código repetitivo para su proyecto. También los configura para que funcionen de la caja. El sitio ofrece una explicación bastante completa de cómo todo encaja.

Con todo el código de la placa de caldera fuera del camino, encontrar soluciones a las preguntas que hizo anteriormente es un poco más fácil. Además, las plantillas permiten coherencia en todos los ámbitos.

EDITAR -> En lo que respecta a los comentarios a continuación, aquí hay una explicación de por qué apoyo este enfoque -> http://stringerstheory.net/the-clean-er-architecture-for-ios-apps/

También este -> Lo bueno, lo malo y lo feo sobre VIPER en iOS


La mayoría de sus preguntas se responden en esta publicación: https://www.ckl.io/blog/best-practices-viper-architecture (proyecto de ejemplo incluido). Le sugiero que preste especial atención a los consejos para la inicialización / presentación de módulos: depende del Router origen hacerlo.

Con respecto a los botones de retroceso, puede use delegates para activar este mensaje en el módulo deseado. Así es como lo hago y funciona muy bien (incluso después de insertar notificaciones push).

Y sí, los módulos definitivamente pueden hablar entre sí mediante el using delegates también. Es imprescindible para proyectos más complejos.