VAYA SIN CONEXIÓN PRIMERO utilizando datos básicos y documentos gestionados

Valentin Chernov se unió a MegaFon como desarrollador de iOS y entró en la tendencia principal de hoy: desconectarse: Valentin está desarrollando una cuenta personal móvil, la aplicación principal de MegaFon. Le permite ver el saldo, cambiar la tarifa, conectar y desconectar servicios y servicios, participar en concursos y utilizar las ofertas personales de los socios de MegaFon.



MegaFon ha elegido la oportunidad de trabajar con comunicaciones inestables como uno de sus importantes puntos de crecimiento. En Rusia hay lugares donde la comunicación se desconecta temporalmente o desaparece durante mucho tiempo. Y es necesario que incluso en este caso la aplicación funcione sin fallos.



Valentin habló sobre cómo se llevó a cabo esta tarea durante los últimos cinco meses, cómo se eligió e implementó la arquitectura del proyecto, qué tecnologías se utilizaron, así como qué lograron y qué se planeó para el futuro, Valentin habló en la Conferencia de Desarrolladores de Aplicaciones Móviles Apps Live 2020.







Una tarea



La empresa dijo: nos desconectamos para que el usuario pueda interactuar con éxito con la aplicación en una conexión de red inestable. Nosotros, como equipo de desarrollo, teníamos que garantizar Offline primero: la aplicación funcionará incluso con una Internet inestable o completamente ausente. Hoy les contaré dónde comenzamos y qué primeros pasos dimos en esta dirección.



Pila de tecnología



Además de la arquitectura MVC estándar, utilizamos:



Rápido + Objetivo-C



La mayor parte del código (80% de nuestro proyecto) está escrito en Objective-C. Y ya escribimos código nuevo en Swift.



Arquitectura modular



Dividimos lógicamente los fragmentos de código global en módulos para lograr una compilación, lanzamiento y desarrollo de proyectos más rápidos.



Submódulos (bibliotecas)



Conectamos todas las bibliotecas adicionales a través del submódulo git para tener más control sobre las bibliotecas utilizadas. Por lo tanto, si el apoyo a alguno de ellos se detiene repentinamente, podremos corregir la situación por nuestra cuenta.



Datos básicos para almacenamiento local



Al elegir, el criterio principal para nosotros fue la natividad y la integración con los marcos de iOS. Y estas ventajas de Core Data fueron decisivas:



  • Guarde automáticamente la pila y los datos que recibimos;
  • , ( , ..)
  • ;
  • ;
  • ;
  • ;
  • UI (FRC);
  • (NSPredicates).


UIManaged document



El kit de interfaz de usuario tiene una clase incorporada llamada UIManagedDocument, que es una subclase de UIDocument. Su principal diferencia es que cuando se inicializa un documento administrado, se especifica una URL para la ubicación del documento en el almacenamiento local o remoto. A continuación, el objeto de documento crea completamente una pila de datos básicos desde el primer momento, que se utiliza para acceder al almacenamiento persistente del documento utilizando el modelo de objeto (.xcdatamodeld) del paquete de la aplicación principal. Es conveniente y tiene sentido, aunque ya vivimos en el siglo XXI:



  • UIDocument guarda automáticamente el estado actual, a una frecuencia específica. Para secciones especialmente críticas, podemos activar manualmente el guardado.
  • . - — , , - , — , , .
  • UIDocument .
  • Core data .
  • iCloud . , .
  • .
  • Se utiliza el paradigma de la aplicación basada en documentos, que representa el modelo de datos como un contenedor para almacenar estos datos. Si miramos el modelo MVC clásico en la documentación de Apple, podemos ver que los datos del núcleo se crearon precisamente para manipular este modelo y ayudarnos a trabajar con datos en un nivel superior de abstracción. A nivel de modelo, trabajamos conectando UIManagedDocument con toda la pila creada. Y consideramos el documento en sí mismo como un contenedor que almacena datos Core y todos los datos del caché (de pantallas, usuarios). Además, pueden ser imágenes, videos, textos, cualquier información.


Consideramos nuestra aplicación, su lanzamiento, autorización de usuario y todos sus datos como una especie de documento (archivo) de gran tamaño que almacena el historial de nuestro usuario:







Proceso



Cómo diseñamos la arquitectura



Nuestro proceso de diseño se desarrolla en varias etapas:



  1. Análisis de especificaciones técnicas.
  2. Representación de un diagrama UML. Utilizamos principalmente tres tipos de diagramas UML: diagrama de clases, diagrama de flujo, diagrama de secuencia. Esta es la responsabilidad directa de los desarrolladores senior, pero los desarrolladores con menos experiencia también pueden hacerlo. Esto incluso es bienvenido, ya que le permite sumergirse bien en la tarea y aprender todas sus sutilezas. Eso ayuda a encontrar fallas en la tarea técnica, así como a estructurar toda la información sobre la tarea. Y tratamos de tener en cuenta la naturaleza multiplataforma de nuestra aplicación: trabajamos en estrecha colaboración con el equipo de Android, dibujamos el mismo diagrama en dos plataformas y tratamos de utilizar los principales patrones de diseño generalmente aceptados del grupo de cuatro.
  3. Revisión de arquitectura. Como regla general, un colega de un equipo adyacente realiza la revisión y evaluación.
  4. Implementación y prueba en el ejemplo de un módulo de interfaz de usuario.
  5. Escalada. Si la prueba tiene éxito, escalamos la arquitectura en toda la aplicación.
  6. Refactorización. Para comprobar si nos perdimos algo.


Ahora, después de cinco meses de desarrollar este proyecto, puedo mostrar todo nuestro proceso en tres etapas: qué sucedió, cómo cambió y qué sucedió como resultado.



Que pasó



Nuestro punto de partida fue la arquitectura MVC estándar; estas son capas interconectadas:



  • Capa de interfaz de usuario, totalmente programada con Objective C;
  • Clase de presentación (modelo);
  • La capa de servicio donde trabajamos con la red.


El indicador de actividad se ubicó en el lugar del diagrama donde el proceso de recepción de datos es sensible a la velocidad de Internet: el usuario quiere un resultado rápido, pero se ve obligado a mirar algunos cargadores, indicadores y otras señales. Estos fueron nuestros puntos de crecimiento en la experiencia del usuario:







Periodo de transicion



Durante el período de transición, tuvimos que implementar el almacenamiento en caché para las pantallas. Pero dado que la aplicación es grande y contiene una gran cantidad de código Objective C heredado, no podemos simplemente tomar y eliminar todos los servicios y modelos insertando código Swift; debemos tener en cuenta que, en paralelo con el almacenamiento en caché, todavía tenemos muchas otras tareas de productos en desarrollo.



Encontramos una manera sencilla de integrarnos en el código actual de la manera más eficiente posible, sin romper nada, y realizar la primera iteración de la manera más fluida posible. En el lado izquierdo del diagrama anterior, hemos eliminado por completo todo lo relacionado con las solicitudes de red: el servicio ahora se comunica con DataSourceFacade a través de la interfaz. Y ahora esta es la fachada con la que trabaja el servicio. Espera del DataSource los datos que recibió previamente de la red. Y en el propio DataSource, la lógica para extraer estos datos está oculta.



En el lado derecho del diagrama, hemos dividido la adquisición de datos en comandos: el patrón de comando tiene como objetivo ejecutar algún comando básico y obtener el resultado. En el caso de iOS, usamos los herederos de NSOperation:







Cada comando que ve aquí es una operación que contiene una unidad lógica de la acción esperada. Se trata de obtener datos de una base de datos (o red) y almacenar estos datos en datos básicos. Por ejemplo, el objetivo principal de AcquireCommand no es solo devolver la fuente de datos a la fachada, sino también permitirnos diseñar código de tal manera que recibamos datos a través de la fachada. Es decir, la interacción con las operaciones pasa por esta fachada.



Y la tarea principal de las operaciones es pasar datos de DataSource a DataSourceFacade. Por supuesto, construimos la lógica de tal manera que muestre los datos al usuario lo más rápido posible. Por lo general, dentro de DataSourceFacade, tenemos una cola operativa donde comenzamos nuestras NSOperations. Dependiendo de las condiciones configuradas, podemos decidir cuándo mostrar los datos de la caché y cuándo recibirlos de la red. Cuando solicitamos por primera vez una fuente de datos en la fachada, vamos a la base de datos de Core, obtenemos los datos de allí a través de FetchCommand (si corresponde) y se los devolvemos instantáneamente al usuario.



Al mismo tiempo, lanzamos una solicitud de datos en paralelo a través de la red, y cuando se ejecuta esta solicitud, el resultado llega a la base de datos, se almacena en ella y luego recibimos una actualización de nuestro DataSource. Esta actualización ya está incluida en la interfaz de usuario. De esta forma minimizamos el tiempo de espera de los datos, y el usuario, al recibirlos al instante, no nota la diferencia. Recibirá los datos actualizados tan pronto como la base de datos reciba una respuesta de la red.



Como se hizo



Pasamos a un esquema más lacónico (y llegaremos al final):







Ahora de esto tenemos:



  • Capa de interfaz de usuario,
  • la fachada a través de la cual proporcionamos nuestro DataSource,
  • el comando que devuelve este DataSource junto con las actualizaciones.


Que es una fuente de datos y por que hablamos tanto de ella



DataSource es un objeto que proporciona datos para la capa de presentación y sigue un protocolo predefinido. Y el protocolo debe ajustarse a nuestra interfaz de usuario y proporcionar datos para nuestra interfaz de usuario (no importa para una pantalla específica o para un grupo de pantallas).



Una fuente de datos normalmente tiene dos responsabilidades principales:



  1. Suministro de datos para su visualización en la capa de interfaz de usuario;
  2. Notificar a la interfaz de usuario de la capa de cambios de datos y enviar el lote de cambios necesarios a la pantalla cuando recibimos una actualización.


Usamos varias variantes del DataSource aquí, porque tenemos mucho código heredado de Objective C, es decir, no podemos colocar fácilmente nuestro Swift DataSource en todas partes. Tampoco usamos colecciones en todas partes todavía, pero en el futuro reescribiremos el código específicamente para usar las pantallas CollectionView.



Un ejemplo de uno de nuestro DataSource:







este es un DataSource para una colección (se llama CollectionDataSource) y esta es una clase bastante simple desde el punto de vista de la interfaz. Toma una colección configurada por un fetchedResultsController y un CellDequeueBlock. Donde CellDequeueBlock es un alias de tipo en el que describimos la estrategia para crear celdas.



Es decir, creamos el DataSource y lo asignamos a la colección llamando a performFetch en fetchedResultsController, y luego toda la magia se asigna a la interacción de nuestra clase DataSource, fetchedResultsController y la capacidad del delegado para recibir actualizaciones de la base de datos:







FetchedResultsController es el corazón de nuestro DataSource. Encontrará mucha información sobre cómo trabajar con él en la documentación de Apple. Como regla general, recibimos todos los datos con su ayuda, tanto los datos nuevos como los que se han actualizado o eliminado. Al mismo tiempo, solicitamos simultáneamente datos de la red. Tan pronto como los datos fueron recibidos y almacenados en la base de datos, recibimos una actualización de DataSource y la actualización nos llegó en la interfaz de usuario. Es decir, con una sola solicitud, recibimos datos y los mostramos en diferentes lugares: ¡genial, conveniente, nativo!



Y siempre que sea posible usar DataSource listo para usar con tablas o con colecciones, lo hacemos:







en aquellos lugares donde tenemos muchas pantallas y no se usan tablas y colecciones (y se usa programación Objective C), evaluamos qué datos necesitamos para la pantalla, ya través del protocolo describimos nuestro DataSource. Después de eso, escribimos la fachada; como regla, este también es un protocolo público de Objective C a través del cual solicitamos nuestro DataSource. Y luego la entrada al código Swift ya está en progreso.



Tan pronto como estemos listos para transferir la pantalla completamente a la implementación de Swift, será suficiente eliminar el contenedor de Objective C y, gracias al DataSource personalizado, podemos trabajar directamente con el protocolo Swift.



Actualmente estamos usando tres variantes principales de DataSources:

  1. TableViewDatasource + estrategia de celda (estrategia para crear celdas);
  2. CollectionViewDatasource + estrategia de celda (opción con colecciones);
  3. CustomDataSource es una opción personalizada. Lo usamos más ahora.




resultados



Después de todos los pasos para diseñar, implementar e interactuar con el código heredado, la empresa recibió las siguientes mejoras:



  • La velocidad de entrega de datos al usuario ha aumentado significativamente debido al almacenamiento en caché. Este es probablemente un resultado obvio y lógico.
  • Ahora estamos un paso más cerca del primer paradigma fuera de línea.
  • Los procesos de una revisión arquitectónica multiplataforma se han establecido dentro de los equipos de iOS y Android: todos los desarrolladores involucrados en este proyecto poseen información e intercambian experiencias fácilmente entre los equipos.
  • . , , legacy , .
  • , — . , , , , , .


La ventaja para nosotros fue que entendimos cómo trabajar con arquitectura y diagramas puede ser interesante y divertido (y esto simplifica el desarrollo). Sí, pasamos mucho tiempo dibujando y alineando nuestros enfoques arquitectónicos, pero en lo que respecta a la implementación, escalamos muy rápidamente todas las pantallas.



Nuestro camino hacia Offline primero continúa: no solo necesitamos el almacenamiento en caché para estar fuera de línea, sino que también el usuario puede operar sin una conexión de red, con una mayor sincronización con el servidor después de que aparezca Internet.



Enlaces





Apps Live 2020 .

— Android iOS, . , , .




All Articles