Ciclo de Vida de un ViewController
El ciclo de vida de un view controller es desde que se va presentar la view hasta que la view desaparece de la jerarquía de vistas. Durante este ciclo, el view controller recibe varias notificaciones, vamos a explicarlas una a una.
Tabla de contenido
Hoy en SwiftBeta vamos a entender el ciclo de vida de un ViewController y vamos a aprender a liberar sus recursos cuando ya no los tenemos en la jerarquía de vistas. Como hemos ido comentando durante la serie de UIKit, un ViewController es la parte fundamental del framework UIKit. Para mostrar las vistas de nuestra app usamos ViewControllers y estos tienen un ciclo de vida. Es decir, un ViewController aparece en el flujo de nuestra app, lo usamos durante un periodo de tiempo y cuando hemos dejado de usarlo, ya sea por que hemos navegado a otra vista de nuestra app, el ViewController se libera, también liberando todos los recursos que estaba consumiendo.
Es muy importante dejar claro que un móvil tiene unos recursos limitados, y uno de ellos es la memoria. Debemos entender este ciclo de vida e impedir que se generen retain cycles, ya que si nuestra app acaba consumiendo mucha memoria, el sistema operativo nos la cerrará de golpe, creando una mala experiencia para nuestros users.
Hoy vamos a ver una serie de métodos que se ejecutan en el ciclo de vida de nuestro ViewController.
Pero antes, apoya el canal suscribiendote, de esta manera seguiré subiendo un video nuevo cada semana, con las ultimas novedades de Swift, SwiftUI y Xcode.
Vamos a crear un proyecto de cero en Xcode y vamos a ver ejemplos prácticos,
Creamos proyecto en Xcode
Lo primero de todo es crear un proyecto en Xcode. Al crear el proyecto seleccionar como interface Storyboard, ya que vamos a trabajar con el framework UIKit.
Una vez creado el proyecto, si vamos a nuestro listado de ficheros, allí vemos el AppDelegate, SceneDelegate y ViewController. Vamos al ViewController.
Dentro del ViewController, vemos que hay un override de un método llamado viewDidLoad. Este método se hereda de UIViewController, es decir nuestro ViewController es una subclase de UIViewController.
Vamos a ver la documentación de UIViewController, para ello pulsamos COMMAND y clickamos en UIViewController. Aquí dentro podemos ver una serie de inicializadores, propiedades y métodos que podemos sobreescribir cuando desarrollemos nuestra app. Y podemos ver el famoso viewDidLoad, exacto el que estábamos haciendo un override en nuestro ViewController.
Volvemos a nuestro ViewController y vamos a ver algunos métodos que se llaman durante el ciclo de vida de nuestro ViewController. Todos los método que vamos a ver se llaman por orden:
viewDidLoad
El primero de todos que vamos a ver es el viewDidLoad, este método se llama para notificar al ViewController de que su vista se ha cargado en memoria. Dentro de este método podemos añadir lógica, añadir subvistas, añadir constraints, etc. Más tarde crearemos un ejemplo real.
Voy a añadir un print dentro de este método.
viewWillAppear
El segundo método que se llama, es el viewWillAppear y es cuando la vista está preparada y se va a mostrar en nuestro ViewController.
viewWillLayoutSubviews
Continuamos con el viewWillLayoutSubviews, este método se llama para notificar al ViewController de que la vista está apunto de posicionar sus subvistas. Luego veremos un ejemplo real, pero si rotaramos nuestro device, este método se llamaría.
viewDidLayoutSubviews
Otro método es el viewDidLayoutSubviews, este método se llama para notificar al ViewController de que la vista acaba de posicionar todas sus subvistas.
Un ejemplo muy claro de cuando se llaman estos dos últimos métodos es cuando rotamos nuestro device, si la app soporta el modo landscape, las vistas se tienen que distribuir por el nuevo tamaño de pantalla. Y dentro de este proceso se llama a los dos métodos anteriores
viewDidAppear
Vamos a continuar, ahora toca el método viewDidAppear. Este método se llamará cuando la vista ya se ha cargado en el ViewController y se está mostrando al user
viewWillDisappear
Todos los que hemos visto antes son al cargar un ViewController. Pero también podemos controlar y ejecutar código cuando un ViewController esté apunto de desaparecer. Esto lo veremos a continuación en un ejemplo real. Pero imagina que que dimisseas un ViewController de tu jerarquía de vistas, entonces se llamaría al método viewWillDisappear para notificar al ViewController de que la vista está apunto de ser eliminada de la jerarquía de vistas.
viewDidDisappear
Y para finalizar, justo después de que se ejecutara el código de viewWillAppear, se ejecutaría el método viewDidDisappear. Con esto recibiríamos una notificación al ViewController de que la vista ya ha sido eliminada de la jerarquía de vistas.
Si compilamos ahora, vamos a ver unos cuantos prints por consola. Se están mostrando todos menos viewWillDisappear y viewDidDisappear. Vamos a añadir una subvista para poder el flujo completo del ciclo de vida de nuestro ViewController.
Añadimos subvistas a nuestro ViewController
Ahora vamos a añadir un UIButton a nuestro ViewController. Para hacerlo vamos ha crearlo por código, como ya hemos visto en otros videos de la serie de UIKit.
Este UIButton nos va a servir para navegar a otro ViewController
¿Cuando se ejecuta viewWillDisappear y viewDidDisappear?
En el método que hemos creado hace un momento, el presentCurrentViewController vamos a presentar nuestro ViewController. Es decir, vamos a presentar el único ViewController que tenemos, cada vez que pulsemos en nuestro código.
Y para que sea más fácil entender que cada vez presentamos una instancia nueva de nuestro ViewController, voy a asignarle un backgroundColor de forma random. Así que dentro del viewDidLoad añadimos la siguiente línea de código:
Si ahora compilamos y pulsamos el UIButton. Vemos como se van presentando UIViewControllers a nuestra jerarquía de vistas. Pero si te fijas aunque vayamos presentando ViewController los métodos viewWillDisappear y viewDidDiappear no se están llamando. Esto es por que todos estos ViewControllers aún están en la jerarquía de vistas. Si quieres eliminarlos, lo único que tienes que hacer es dismissearlos. Al cerrar un ViewController, vemos como aparece por consola el siguiente mensaje:
viewWillDisappear 1
viewDidDisappear 1
Hasta aquí todo bien, pero cómo podemos saber que los recursos de nuestro ViewController se están liberando correctamente?
deinit
Podemos usar un método llamado deinit. Igual que tenemos los init para inicializar y crear instancias de nuestras clases, deinit se llama cuando el ViewController ha sido liberado en memoria. Vamos a verlo, justo vamos a añadir un método llamado deinit al principio de nuestro ViewController:
Si ahora compilamos y vamos presentando ViewControllers en nuestra jerarquía de vistas, al dimissear uno de ellos ¿qué ocurre? pues que el método deinit no se está ejecutando, pero ¿por qué? por que tenemos un retain cycle. Si no sabes lo que es un retain cycle lo explico en este video super interesante, y te invito a que le eches un vistazo. Ya que seguro aprendes cosas nuevas para tu carrera como iOS Developer.
Lo que debemos hacer ahora es romper este retain cycle y así liberar los recursos de nuestro ViewController, para hacerlo, yo ya tengo identificado donde ocurre, es decir, donde tenemos el problema. Tenemos que ir a la configuración de nuestro swiftBetaButton y aquí tan solo tenemos poner [weak self] en el closure de la acción que se ejecuta al pulsar el UIButton:
Ahora si repetimos el mismo proceso de antes, vamos a mostrar varios ViewController y al dismisear uno de ellos obtenemos el siguiente mensaje por consola:
viewWillDisappear 1
viewDidDisappear 1
🧹
Es importante entender que, aunque estemos dismiseando un ViewController, esto no significa que se esté liberando.
Antes de acabar con el video, me gustaría ver un ejemplo real de llamadas a viewWillLayoutSubviews y viewDidLayoutSubviews. En lugar de rotar el device para que se ejecuten, vamos hacer una cosa muy chula
¿Cuando se ejecuta viewWillLayoutSubviews y viewDidLayoutSubviews?
Vamos a crear una de las constraints de UIButton, y vamos a modificarla para que cuando se pulse el UIButton se modifique la constraint centerYAnchor. Para hacerlo voy a crear una propiedad:
var buttonConstraint: NSLayoutConstraint?
Voy a modificar el viewDidLoad
y finalmente actualizamos el método que se lanza cuando pulsamos el UIButton.
si ahora compilamos y probamos el código, vemos que cada vez que pulsamos el UIButton, se llaman a los métodos viewWillLayoutSubviews y viewDidLayoutSubviews
Para acabar, me gustaría explicar rápidamente que un ViewController tiene más método que se llaman cuando pasan ciertas acciones
didReceiveMemoryWarning
Una de ellas es cuando nuestra app está consumiendo demasiada memoria. Antes de que el sistema cierre nuestra app, hay un método llamado didReceiveMemoryWarning que es llamado. Aquí podemos aplicar lógica para que antes de que se cierre la app se ejecute. Vamos a poner un print
Vamos a compilar y vamos a ver cómo podemos ejecutar este método. Desde el simulador, vamos a una de las opciones del menu Debug -> Simulate Memory Warning.
Al seleccionar esta opción se ejecuta el método didReceiveMemoryWarning de nuestro ViewController, printando el mensaje que habíamos especificado.
Conclusión
Hoy hemos aprendido sobre el ciclo de vida de un UIViewController. Es importante tener esto en cuenta para crear apps que sean eficientes y vayan liberando los recursos que ya no necesitan. Hemos aprendido a usar los métodos que se llaman para cargar la View y también los método que se llaman cuando la View va a desaparecer de la jerarquía de vistas. También hemos visto dos métodos que se llaman cuando las subvistas de una View se tienen que redistribuir en la vista padre.
Para finalizar, hemos visto otro método que notifica al ViewController cuando hay un memory warning.