Evolución de la arquitectura móvil de Reddit



Este es el primero de los artículos donde hablamos sobre la arquitectura de la aplicación Reddit para iOS. Aquí estamos hablando de la funcionalidad que funciona más cerca de la interfaz de usuario. En particular, la transición a la arquitectura Model-View-Presenter (MVP). Las ventajas de tal refactorización:

  • Mejora de la flexibilidad, claridad y facilidad de mantenimiento del código para respaldar el crecimiento futuro y acelerar las iteraciones.
  • Aumento del rendimiento de desplazamiento en 1,58 veces.
  • Prueba de unidad estimuladora. El número de pruebas aumentó de unas pocas a más de 200.

A continuación se muestra un diagrama visual de nuestra arquitectura en capas. El primer artículo se centrará en los niveles de Vista y Presentador.


La mejor vista de nuestra arquitectura en capas

Prerrequisitos para el cambio


Hace más de un año, publicamos el artículo "Creación de una cinta de opciones en una aplicación Reddit para iOS" . Discutió cómo generar una alimentación productiva y expandible con una notable tasa de sesión del 99.95% sin fallar. Explicamos cómo usar la arquitectura Modelo-Vista-Controlador (MVC) y crear abstracciones para los datos de paginación.

Ahora Reddit ha crecido y continúa creciendo como organización y como servicio. En consecuencia, los requisitos para la aplicación Reddit iOS han aumentado. Debería admitir más solicitudes de funciones, bucles iterativos más rápidos y estándares de calidad más altos. El equipo de desarrollo ha crecido de tres a más de veinte personas. La arquitectura MVC original apenas cumple con estos requisitos, por lo que hubo que hacer cambios arquitectónicos.

La esencia del problema


Con el tiempo, el código ha perdido flexibilidad y claridad. En la comunidad de desarrolladores de iOS, la abreviatura MVC a menudo se descifra como Massive View Controller porque los controladores de vista a menudo se hinchan a objetos divinos con más de mil líneas. A pesar de todos nuestros esfuerzos, surgió un problema: la jerarquía de la herencia se hizo incómodamente profunda, y los controladores comenzaron a convertirse en oscuros objetos divinos que resisten el cambio.

Introdujimos el último clavo en el ataúd MVC cuando decidimos cambiar el nivel de presentación de la cinta. La audiencia de la aplicación Reddit está creciendo, por lo que el rendimiento del desplazamiento se ha degradado con demasiada frecuencia de 60 FPS a 45–55 FPS. Esto significa que debe volver a escribir la capa de presentación de la cinta mientras mantiene la implementación original. Pero en la arquitectura MVC existente, no podríamos reescribir la capa de presentación de cinta sin duplicar miles de líneas de código.

Además, muchas partes de la base del código son difíciles de probar. El código está en la clase probada de la capa de presentación, y las dependencias a menudo están solas (singleton) o codificadas en la clase misma. Queríamos darnos cuenta de la posibilidad de una prueba normal.

Otras tareas establecidas para la refactorización: el número de fallas debe permanecer bajo, la nueva arquitectura debe sentar las bases para el crecimiento futuro y no interferir con las funciones que dependen de la infraestructura existente. Es decir, se requieren cambios evolutivos, no revolucionarios.

Transición a MVP


Decidimos que para resolver los problemas anteriores se necesita una nueva versión de la aplicación. Después de considerar varias opciones, decidimos usar la arquitectura Model-View-Presenter (MVP). MVP cumple con todos estos criterios, y es una arquitectura bien conocida y documentada, por lo que es más fácil capacitar ingenieros. También conserva el concepto de "ver modelos". Si es necesario, en Presenter, puede crear objetos de modelo de vista según el principio de responsabilidad exclusiva , y usarlos para expandir nuestras vistas.


Gráfico de modelo-vista-presentador

Deshacerse de Massive View Controller


Para las aplicaciones de iOS, se acepta que los objetos de vista son subclases de UIView, los objetos de controlador son subclases de UIViewController y los objetos de modelo son objetos simples. Como el nombre UIViewController implica, aquí la vista y el controlador se combinan en un solo objeto. Es decir, el modelo MVC en iOS a menudo pierde sus ventajas debido a la estrecha conexión entre el nivel de presentación y el controlador. Curiosamente, la propia Apple reconoce esta conexión .


A menudo, la arquitectura Modelo-Vista-Controlador para iOS se convierte en

En la arquitectura MVP, tomamos en cuenta este concepto y lo formalizamos, considerando el UIViewController realmente como un objeto explícito de la capa de presentación. El concepto de tratar un UIViewController como un objeto de presentación con un mal nombre se ha popularizado en los últimos años.

Entonces, eliminamos toda lógica extraña en nuestros UIViewControllers. Luego asignamos Presentador al intermediario entre la vista y el modelo. En este nuevo rol, desconoce los objetos de vista como el UIViewController. Tenga en cuenta que Presenter interactúa con la vista a través de una interfaz. Teóricamente, puede cambiar la implementación de una vista a NSViewController (para MacOS), etc.


Hacemos que ViewController sea más fácil al presentar Presentador y compartir responsabilidades

Reflexionando sobre las opciones de MVP


Como puede ver en el diagrama MVP, la arquitectura salió muy similar a MVC. De hecho, hay más similitudes que diferencias. Dicha arquitectura simplemente ayuda a establecer la separación correcta del código de presentación y la lógica empresarial que MVC busca. De hecho, todas las derivadas de la arquitectura de MV (x), como MVP, MVVM, MVAdapter y otras, son simplemente versiones diferentes del mismo concepto.

Uno puede preguntarse por qué abandonamos completamente MVC. De hecho, Apple describe diferentes tipos de controladores : para modelos, intermediarios y coordinación. Honestamente, tal vez podríamos reemplazar nuestro presentador con otro controlador. Pero decidieron no hacer esto, porque la mayoría de los desarrolladores de iOS por alguna razón formaron la creencia de que UIViewController es un sinónimo del controlador. Usando la palabra Presentador, damos una señal de que este objeto es significativamente diferente de un controlador convencional con un cierto conjunto de funciones y propiedades.

Mejora de la flexibilidad, la sostenibilidad y la claridad.


"Prefiero la composición sobre la herencia" es un mantra bien conocido en la programación de objetos. Con la herencia, debe predecir el futuro y construir una gran taxonomía de objetos. Pero si su jerarquía de herencia construida "idealmente" comienza a desmoronarse debido a cambios imprevistos, es difícil modificar esta estructura rígida. En la composición, los objetos se crean a partir de otros objetos y delegan trabajo en ellos. Esto es útil porque es fácil cambiar el comportamiento de un objeto en tiempo de ejecución simplemente cambiando los objetos que lo componen. Estos objetos compuestos son aún más comprensibles, ya que el código se ve obligado a salir de la jerarquía de herencia a una abstracción orientada a una tarea específica.

Dicha composicionalidad es una de las principales ventajas que la arquitectura MVP nos ha brindado. Ahora puede cambiar el comportamiento del controlador simplemente cambiando la composición de un presentador en particular. Ahora estamos menos preocupados por descifrar la estructura de herencia compleja y rígida. Finalmente, los controladores de vista y los objetos Presentador son más fáciles de entender porque tienen un conjunto de tareas más claro.

Al introducir Presenter y mover parte de la lógica del controlador de vista allí, hemos simplificado la jerarquía de herencia del controlador. La siguiente figura muestra que pudimos eliminar la clase GalleryFeedViewController, ya que pusimos toda esta lógica en el Presentador. Como ya se discutió, dicha jerarquía de herencia es más fácil de entender y menos rígida.


Simplifique la jerarquía de la herencia a través de la composición.

Cambio libre a la implementación de la capa de presentación


Como se discutió anteriormente, el rendimiento de desplazamiento de la cinta comenzó a disminuir de 60 FPS a 45–55 FPS. Por lo tanto, para la capa de presentación de cinta, decidimos usar Texture . Es una plataforma de código abierto basada en Apple UIKit que mejora el rendimiento de la interfaz a través del preprocesamiento en el hilo de fondo. En la arquitectura MVC anterior, no podíamos cambiar la implementación de la capa de presentación sin mucha duplicación de código.


Antes de implementar MVP, tenía que duplicar código extraño en el ViewController que no estaba relacionado con la Vista (naranja)

La nueva arquitectura MVP permitió la introducción del soporte Texture, en lugar de reescribir las cosas desde cero. Acabamos de poner toda la lógica no vista en la clase genérica Presenter. Luego escribieron una nueva implementación de la capa de presentación de texturas c y reutilizaron el código del presentador. Esto dio soporte a ambas implementaciones de View hasta que llegó el momento de desplegar cómodamente la cinta con Texture para todos los usuarios.


Después de la implementación de MVP: el código sin vista se movió al presentador compartido

Cual es el resultado? El siguiente diagrama muestra un aumento en el rendimiento del desplazamiento de la cinta. Queríamos permanecer alrededor de 60 FPS para lograr un desplazamiento absolutamente suave.



Prueba unitaria


Por supuesto, implementamos pruebas unitarias no solo por MVP, sino que fue un factor importante. En particular, la arquitectura MVP ha expandido el área de prueba moviendo el código a un nivel donde es más fácil de verificar. Un efecto secundario es que los niveles de Vista se han vuelto más simples y, por lo tanto, deben probarse con menos frecuencia.


Aumentar el área de prueba después de mover el código no View fuera de esta capa

Las pruebas unitarias han mejorado el soporte de la base de código: le permiten realizar cambios con mayor confianza y ayudan a comprender cuál debería ser el comportamiento correcto. También hacen que el código sea más flexible y comprensible porque fomentan métodos como la inyección de dependencia, la composición y la programación de abstracción. El número de pruebas unitarias ha aumentado de unas pocas a más de 200.

Análisis crítico de MVP en Reddit


Aunque cambiar a MVP ha ayudado mucho, todavía hay algunas cosas a considerar.

La transición de la cinta a Texture causó nuevos problemas con los hilos. La aplicación no admitía inicialmente la implementación asincrónica de View. Es decir, los errores aparecen inevitablemente en el caso de una falta de coincidencia entre el estado Ver y el estado de la aplicación. Por ejemplo, una vista de cinta puede tener N registros. Y en el hilo de fondo, el estado de la aplicación ha cambiado silenciosamente, y ahora contiene menos de N mensajes. Si la discrepancia no se resuelve, la aplicación simplemente se bloqueará cuando View intente mostrar la enésima publicación en la transmisión.

Los errores más difíciles de solucionar con hilos. Son difíciles de reproducir, por lo que son difíciles de depurar. Tuve que cambiar la lógica de la solicitud y recibir datos para ver el feed. En particular, implementamos "protección" con la prohibición de cualquier cambio en la fuente de datos cuando la Vista de la cinta sufre algunos cambios. Esta y otras correcciones menores redujeron la cantidad de errores asociados con el procesamiento de transmisión. Sin embargo, el subprocesamiento múltiple asíncrono todavía se puede mejorar.

En segundo lugar, la capa Presentador representa un "paso" adicional en la tubería. Este paso tiene un precio en términos de aumentar la complejidad del código y reducir el rendimiento. A veces solo quieres ejecutar esta lógica en el UIViewController por capricho o porque estás acostumbrado a hacerlo. En el peor de los casos, encontrará que Presenter está presente simplemente como una entidad, sin ninguna lógica significativa. En tal situación, el presentador no parece justificar su existencia.


A veces puede cambiar de una capa de Vista a una capa de RedditCore sin Presentador

De hecho, nuestra aplicación no está completamente convertida a la arquitectura MVP. Primero, convertir cada UIViewController individual en un Presentador requerirá demasiado tiempo, y no será evolutivo. En segundo lugar, como se mencionó en el párrafo anterior, a veces simplemente no se necesita Presentador. Como descubrimos en nuestro trabajo sobre la implementación de Texture for Ribbon, Presenter es excelente para facilitar MVC masivo o para implementar View con comportamiento variable, o si tiene una lógica compleja que debe verificarse. Pero a veces el UIViewController es tan simple que Presenter no tiene sentido. Entonces es opcional. El presentador solo debe implementarse si es necesario.

Resumen y planes futuros


Refactorizar la arquitectura MVP en la aplicación Reddit iOS ayudó a resolver muchas de las tareas. Al presentar la capa Presenter, desarrollamos gradualmente la arquitectura de la aplicación para admitir la nueva implementación de la capa de presentación, sin interrumpir otras funciones. El código se ha vuelto más claro al facilitar el "MVC masivo", transfiriendo la lógica extraña a la capa Presentador. También les dimos a los desarrolladores la capacidad de iterar más rápido e implementar nuevas funciones. Y mejoró significativamente las pruebas.

Dado todo esto, todavía queda un largo camino por recorrer. Continuamos creando objetos de Presentador y los mejoramos. Necesitamos continuar moviendo la lógica extraña de UIViewControllers al nivel de Presentador. También es necesario que todos los presentadores estén mejor alineados con el principio de responsabilidad exclusiva. Al final, tanto la aplicación como la arquitectura evolucionan constantemente.

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


All Articles