Advertisement
  1. Code
  2. iOS SDK

La Manera Correcta de Compartir Estados Entre Controladores de Vista en Swift

by
Read Time:13 minsLanguages:

Spanish (Español) translation by Cristian Marroquin (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

Unos años atrás, cuando era aun un empleado en una  agencia de consultoría móvil, trabaje en una aplicación para un gran banco de inversiones. Las grandes compañías, especialmente los bancos, usualmente tienen los procesos en sito para asegurarse de que su software es seguro, robusto y mantenible.

Parte de este proceso involucra enviar el código de la aplicación que escribí a un tercero para revisión. Eso no me molestó, porque pensé que mi código era impecable y que la compañía que lo revisara diría lo mismo.

Cuando obtuve una respuesta, el veredicto fue diferente de lo que pensaba. Aunque ellos dijeron que la calidad del código no estaba mal, ellos apuntaron el hecho de que el código era difícil de mantener y probar (las prueba  unitarias no eran populares en el desarrollo de iOS en aquellos días).

Descarté su crítica, pensando que mi código era muy bueno y que no había manera que podría ser mejorado. ¡Ellos no entendieron!

Tuve la típica arrogancia de un desarrollador: A veces pensamos que lo que hacemos está bien y que los otros no lo entienden.

En retrospectiva estaba equivocado. Mucho más luego, comencé a leer acerca de algunas buenas prácticas. Desde entonces, los problemas en mi código comenzaron a mostrarse como un dolor en el pulgar. Me di cuenta, como muchos desarrolladores de iOS, he sucumbido ante algunos errores clásicos que algunos programadores cometen.

Lo que Muchos Desarrolladores iOS no Entienden

Una de las malas practicas mas comunes en el desarrollo de iOS surge al pasar estados entre los controladores de vista de una aplicación. Yo mismo he caído en esta trampa en el pasado.

La propagación del estado a través de los controladores de vista es vital en cualquier aplicación de iOS. Mientras tus usuarios navegan por las pantallas de tu aplicación e interactúan con ella, necesitas mantener un estado global que siga todos los cambios que el usuario hace a los datos.

Y esto es donde muchos desarrolladores de iOS llegan a la obvia, pero incorrecta, solución: el patrón singleton

El patrón singleton es muy rápido de implementar, especialmente en Swift, y funciona bien. Solo tienes que añadir una variable estática a una clase para mantener una instancia compartida de la clase misma, y esta listo.

Esta instancia es fácil de acceder desde cualquier lugar en el código.

Por esta razón, muchos desarrolladores piensan que encontraron la mejor solución al problema de propagación de estado. Pero están equivocados.

El patrón Singleton es en realidad considerado un anti-patrón. Han habido muchas discusiones de esto en la comunidad de desarrollado. Por ejemplo puedes ver esta pregunta en Stack Overflow.

En pocas palabras, los singletons crean estos problemas:

  • Introducen muchas dependencias en tus clases, haciéndolas difícil de cambiar en el futuro.
  • Hacen el estado global accesible desde cualquier parte de tu código. Esto puede crear interacciones complejas  que son difíciles de seguir y causar errores inesperados.
  • Hacen que tus clases sean muy difíciles de probar, ya que no puedes separarlas de un singleton fácilmente.

En este punto, algunos desarrolladores piensan: "Ah, Tengo una mejor solución. Voy a usar en su lugar el AppDelegate"

El problema es que en las aplicaciones de iOS la clase AppDelegate  es accedido a través de la instancia compartida UIApplication:

Pero la instancia compartida de UIApplication es en si un singleton. Así que no has resuelto ¡nada!

La solución a este problema es la inyección de dependencia. Inyección de dependencia significa que una clase no recupera o crea sus propias dependencias, sino que las recibe desde afuera.

Para ver como se usa la inyección de dependencia en las aplicaciones de iOS y como pueden permitir el compartir el estado, primero revisaremos uno de los patrones arquitectónicos fundamentales de las aplicaciones de iOS: el patrón Modelo-Vista-Controlador.

Extendiendo el Patrón MVC

El patrón MVC, en pocas palabras, indica que hay tres capas en la arquitectura de una aplicación de iOS:

  • La capa de Modelo representa los datos de una aplicación.
  • La capa de Vista muestra la información en la pantalla y permite interacción.
  • La capa de Controlador actúa como unión entre las otras dos capas, moviendo los datos entre ellos.

La representación usual del patrón MVC es algo como esto:

Simplistic view of the MVC patternSimplistic view of the MVC patternSimplistic view of the MVC pattern

El problema es que este diagrama esta equivocado.

Este "secreto" oculto a simple vista en un par de lineas en la documentación de Apple:

"Uno puede combinar los roles de MVC que le corresponden a un objeto, haciendo un objeto, por ejemplo, cumplir con los roles de controlador y de vista — en tal caso, seria llamado controlador de vista. de la misma forma, puedes tener objetos modelo-controlador."

Muchos desarrolladores piensan que los controladores de vista son los únicos controladores que existen en una aplicación de iOS. Por esta razón, un montón de código termina escrito dentro de ellos por falta de un mejor lugar. Esto es lo que lleva a los desarrolladores al uso de singletons cuando ellos necesitan propagar el estado: esto parece la única solución posible.

De las lineas citadas arriba, esta claro que podemos agregar una nueva entidad a nuestro conocimiento del patrón MVC: el modelo controrolador Los controladores de Modelo se encargan del modelo de la aplicación, cumpliendo con los roles que el modelo por si mismo no es capaz. El es quema anterior realmente debería verse así:

Diagram of the MVC pattern updated with view and model controllersDiagram of the MVC pattern updated with view and model controllersDiagram of the MVC pattern updated with view and model controllers

El ejemplo perfecto de cuando un controlador de modelo es útil es al mantener el estado de la aplicación. El modelo debe representar solamente los datos de tu aplicación. El estado de la aplicación no debe ser problema de el.

La conservación del estado usualmente termina dentro de los controladores de vista, pero ahora tenemos un nuevo y mejor lugar para colocarlo: un controlador de modelo. Este controlador de modelo puede ser transferido a los controladores de vista en cuanto aparezcan en pantalla mediante inyección de dependencia.

Hemos resuelto el anti-patrón singleton. Vamos a ver nuestra solución en practica con un ejemplo.

Propagando el Estado A través de Controladores de Vista Usando Inyección de Dependencia.

Vamos a escribir una aplicación sencilla para ver un ejemplo concreto de como funciona esto. La aplicación va  mostrar tu cita favorita en pantalla, y permitirte editar la cita en una segunda pantalla.

Esto significa que nuestra aplicación va a necesitar dos controladores de vita, los que necesitaran intercambiar el estado. Luego de ver como funciona esta solución, puedes expandir el concepto a otras aplicaciones de cualquier tamaño y complejidad.

Para empezar, necesitamos un modelo que represente los datos, en nuestro caso es una cita. Esto puede ser echo con una estructura simple :

El Controlador de Modelo

Necesitamos crear un controlador de modelo que contenga el estado de la aplicación. Este controlador de modelo necesita ser una clase. Esto es porque vamos a necesitar una sola instancia que vamos a pasar a todos nuestros controladores de vista. Los Tipos de valor como structs son copiados cuando son pasados, entonces ellos claramente no son la solución. 

Todo lo que nuestro controlador de modelo necesita en nuestro ejemplo es una propiedad donde mantener la cita actual. Pero, por supuesto, en grandes aplicaciones los controladores de modelo pueden ser mas complejos que esto:

He asignado un valor predeterminado al valor de la propiedad cita así tendremos algo que mostrar en pantalla cuando la aplicación se inicie. Esto no es necesario, y puedes declarar la propiedad para ser opcional inicializada a nil, si deseas que tu aplicación se inicie en un estado en blanco.

Crea la Interfaz de Usuario

Tenemos ahora el controlador de modelo, el cual contendrá el estado de nuestra aplicación. Ahora, necesitamos los controladores de vista que van a representar las pantallas de nuestra aplicación.

Primero, creamos las interfaces de usuario respectivas. Esto es como los dos controladores de vista se ven en el storyboard de la aplicación.

view controllers in the storyboardview controllers in the storyboardview controllers in the storyboard

La interfaz del primer controlador de vista esta echo de un par de etiquetas y botones, puestas juntas con simples restricciones de orden automático ( puedes leer mas sobre arreglo automático aquí en Envato Tuts+.)

La interfaz del segundo controlador de vista es lo mismo, pero tiene un text view para editar el texto de la cita y un campo de texto para editar el autor. 

Los dos controladores de vista están conectados por una sola transición de presentación modal, la cual se origina desde el botón de Editar cita.

Puedes explorar la interfaz y las restricciones del controlador de vista en el repo de GitHub.

Codificar un Controlador de Vista con Inyección de Dependencia

Ahora necesitamos codificar nuestros controladores de vista. Lo importante a tener en mente aquí es que ellos necesitan recibir la instancia del controlador de modelo desde afuera, a través de inyección de dependencia. Así que ellos necesitan exponer una propiedad para este fin.

Podemos llamar a nuestro primer controlador de vista QuoteViewController. Este controlador de vista necesita en su interfaz un par de salidas para las etiquetas de cita y autor.

Cuando este controlador de vista aparezca en pantalla, vamos a llenar su interfaz para mostrar la cita actual. Ponemos el código para hacer esto en el método viewWillAppear(_:) del controlador.

Pudimos haber puesto este código dentro del método viewDidLoad() en su lugar, lo que es bastante común. El problema, sin embargo, es que viewDidLoad() es invocado una sola vez, cuando el controlador de vista es creado. En nuestra aplicación, necesitamos actualizar la interfaz de QuoteViewController cada vez que aparezca en pantalla. Esto es debido a que el usuario puede editar la cita en la segunda pantalla.

Esto es por lo que usamos el método viewWillAppear(_:) en lugar de viewDidLoad(). De este modo podemos actualizar la UI del controlador de vista cada vez que aparezca en la pantalla. Si quieres saber mas acerca del ciclo de vida de los controladores de vista y todos los métodos que son invocados, He escrito un articulo detallandolos todos.

El controlador de la vista para la Edición

Ahora necesitamos codificar el segundo controlador. Que llamaremos EditViewController.

Este controlador de vista es como el anterior:

  • Tiene salidas para el text view y el cuadro de texto que el usuario va a usar para editar la cita.
  • Tiene una propiedad para la inyección de dependencia de la instancia del controlador de modelo. 
  • Llena su interfaz de usuario antes de aparecer en pantalla.

En este caso, usé el método viewDidLoad() por que este controlador de vista solo aparece en pantalla una vez.

Compartiendo el Estado

Ahora necesitamos pasar el estado entre los dos controladores y actualizarlo cuando el usuario edite la cita.

Pasamos el estado de la aplicación en el método prepare(for:sender:) de QuoteViewController. Este método es desencadenado por la transición cuando el usuario toca el botón Editar Cita.

Aquí pasamos la instancia de ModelController que contiene el estado de la aplicación. Es aquí donde ocurre la inyección de dependencia para el EditViewController.

En el EditViewController, tenemos que actualizar el estado de la recién ingresada cita antes que regresemos al controlador de vista anterior. Podemos hacer esto en una acción conectada al botón Guardar

Inicializar  el Controlador de Modelo

Casi estamos listos, pero puede que hayas notado que aun nos falta algo: el QuoteViewController pasa el ModeController al EditViewController mediante inyección de dependencia. Pero ¿quién le da esta instancia al QuoteViewController en primer lugar? Recuerda que cuando usamos inyección de dependencia, un controlador de vista no debería crear sus propias dependencias. Esta necesita venir desde fuera.

Pero no hay otro controlador de vista antes de QuoteViewController, porque este es el primer controlador de vista de nuestra aplicación. Necesitamos algunos objetos mas para crear la instancia del ModelController y pasarla al QuoteViewController.

Este objeto es el AppDelegate. El rol del delegado de la aplicación es responder  a los métodos del ciclo de vida de la aplicación y configurar la aplicación acordemente.  Uno de estos métodos es application(_:didFinishLaunchingWithOptions:), el cual es invocado tan pronto como la aplicación se lanza. Es aquí donde crearemos la instancia del ModelController y la pasaremos al QuoteViewController:

Nuestra aplicación esta ahora completa. Cada controlador de vista recibe acceso al estado global de la aplicación, pero no usamos singletons en ningún lugar en nuestro código.

Puedes descargar el proyecto de Xcode para esta aplicación de ejemplo en el repo del tutorial en GitHub.

Conclusiones

En este articulo has visto como usando singletons para propagar el estado de una aplicación de iOS es una mala practica. Los Singletons crean muchos problemas, a pesar de ser muy fáciles de crear y usar.

Resolvimos este problema viendo de cerca el patrón MVC y entendiendo las posibilidades ocultas en el. Mediante el uso de controladores de modelo e inyección de dependencia, fuimos campases de propagar el estado de la aplicación a través de todos los controladores de vista sin usar singletons.

Esta aplicación de ejemplo es simple, pero el concepto puede generalizarse a aplicaciones de cualquier complejidad. Este es una buena practica habitual para propagar el estado en las aplicaciones de iOS. Yo la uso en cada aplicación que escribo para mis clientes.

Unas cuantas cosas a tener en mente cuando expandas el concepto a aplicaciones mas grandes:

  • El controlador de modelo puede almacenera el estado de la aplicación, en un archivo por ejemplo. De este modo, nuestros datos serán almacenados cada vez que cerremos la aplicación. Puedes también usar almacenamiento mas complejo, por ejemplo con Core Data. Mi recomendación es mantener esta funcionalidad en un controlador de modelo separado que solamente se haga cargo del almacenamiento. Ese controlador puede ser usado por el controlador del modelo para mantener el estado de la aplicación.
  • En una aplicación con un flujo mas complejo, tendrás muchos contenedores en tu flujo de la aplicación. Estos son usualmente controladores de navegación, con el ocasional controlador de barra de pestañas. El concepto de inyección de dependencia aun aplica, pero no necesitas tomar en cuenta los contenedores. Puedes ya sea indagar en sus controladores de contenido de vista cuando realices la inyección de dependencia, o crea una subclase contenedor personalizada que pase el controlador de modelo.
  • Si agregas interconexión a tu aplicación, esta debería ir en un controlador de modelo separado también. Un controlador de vista puede realizar una solicitud de red a través de su controlador de red y pasar los datos resultantes al controlador de modelo que mantiene el estado. Recuerda que el rol de un controlador de vista es exactamente este: actuar de unión entre un objeto que pasa datos entre objetos.

Mantente al tanto para mas consejos y buenas practicas para el desarrollo de aplicaciones de iOS.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.