Async/Await en Swift nos permite tener un código mucho más lineal que usando los típicos callbacks (o completion handlers). Con Async/Await podemos mejorar nuestro código para que sea más legible, entendible y fácil de mantener.
Hoy en SwiftBeta vamos a ver la diferencia entre usar callbacks o Async/Await. Un pequeño dato es que Async/Await fue introducido en Swift 5.5 y vas a ver por qué es un gran avance.
Vamos a crear una app que haga varias peticiones HTTP a la API de Rick and Morty. Haremos 3 peticiones, pero el único requisito es que se deberán hacer una consecutiva de la otra, de esta manera podremos crear un modelo y éste se mostrará en la vista. Para hacerlo vamos a crear un proyecto con el framework SwiftUI
Creamos proyecto en Xcode
Lo primero de todo que vamos hacer es crear nuestro proyecto en Xcode. Cuando lo creemos vamos a crear como interfaz SwiftUI. Y una vez creado, vamos a crear un nuevo fichero, y lo vamos a llamar ViewModel.
Dentro de nuestro ViewModel vamos a crear un método que se va a encargar de realizar varias peticiones HTTP, en total va a llamar a 3 endpoints diferentes:
Character, vamos a llamar a este endpoint para sacar la información de Rick. Uno de los principales personajes de Rick & Morty.
Episodes, vamos a llamar a este endpoint para sacar el título del primer episodio en el que aparece Rick
Location, vamos a llamar a este endpoint para sacar la información de la localización de Rick, queremos saber la dimensión en la que se encuentra.
Lo primero de todo, vamos a crear estas llamadas, estas peticiones HTTP, con callbacks. Para mapear el JSON de cada petición, crearemos un modelo diferente, para empezar vamos a crear una Struct llamada CharacterModel conformando el protocolo Decodable, y para saber qué propiedades necesitamos, vamos a ver la documentación de la API que vamos a usar:
Vamos a crear nuestro modelo:
Una vez hemos creado el modelo, vamos a crear nuestra primera petición con nuestro primer callback:
Para probar que funciona nuestra petición, vamos a crear una instancia en nuestra vista ContentView:
Al compilar, vemos que la petición se realiza correctamente. Ahora, vamos a crear otra petición, esta peticón nos dará el título del primer episodio donde aparece Rick (miramos la documentación de la API para crear un modelo):
Creamos petición HTTP con Callback (CompletionHandler)
Y ahora creamos la petición HTTP dentro de la que hemos creado hace un momento:
Si ahora compilamos la app, vemos que se realiza correctamente la primera llamada y la segunda. Ahora vamos a crear una tercera petición, queremos saber en qué dimensión se encuentra nuestro personaje. Y para saberlo, debemos relizar una petición a la URL de la propiedad locationURL de nuestro CharacterModel.
Primero creamos LocationModel:
Y a continuación creamos la petición HTTP dentro de la anterior:
Ahora vamos a crear un modelo llamado CharacterBasicInfo, este modelo se creará a partir de la información que hemos obtenido de las 3 peticiones HTTP, y tan solo tendrá 3 propiedades.
Ahora que ya tenemos el modelo, vamos a:
Crear una propiedad @Published con el valor de .empty
Darle un valor en el último callback, en la última petición HTTP que hemos realizado.
Y el resultado sería el siguiente:
Ahora vamos hacer que esta información se muestra en nuestro ContentView:
Fíjate que aunque nuestra app funciona, el método de nuestro ViewModel es muy dificil de seguir y de entender. Hemos tenido que realizar las 3 peticiones de forma consecutiva para construir el modelo CharacterBasicInfo, y visualmente, lo que hemos creado con tantos callbacks, se conoce como Pyramid of Doom. Si tuvieramos que añadir otra petición dentro de la 3ra, se volvería aún más complicado seguir el código y entender qué ocurre.
Migración a Async/Await en Swift
Con Async/Await podemos simplificarlo muchísimo haciendo que sea mucho más fácil de entender y de seguir para todo developer. Vamos a ver cómo refactoriamos este código. Lo primero de todo que vamos hacer es comentar el código de las 3 peticiones, y vamos a empezar por la primera petición. Voy a crear la primera petición HTTP con Async/Await y luego comento el código:
Aquí vamos a fijarnos en varias partes:
Hemos usado URLSession.shared.data y la URL para realizar una petición HTTP. Un detalle curioso es que no necesitamos llamar al método resume()
La firma de nuestra función la hemos modificado, ahora tiene la keyword async
El resultado de la petición la asignamos a una tupa, el primer campo es data y el segundo el response (por simplicidad del video no estamos teniendo en cuenta la gestión de errores)
La siguiente línea hace el parseo de data a nuestro modelo de dominio.
Si intentamos compilar tenemos un error, debemos modificar nuestra vista ContentView. Dentro del modificador onAppear, lo modificamos de la siguiente manera:
Vamos a compilar y vamos a ver qué ocurre. Vemos por consola que el modelo characterModel es correcto, el mismo resultado que teniamos con los callbacks. Ahora vamos a continuar, y vamos a realizar las siguientes 2 peticiones:
Si compilamos, observamos que las 3 peticiones se realizan correctamente, ahora podemos crear el modelo CharacterBasicInfo.
Como has podido comprobar, tenemos muchas menos líneas que antes y es mucho más fácil de seguir.
Conclusión
Desde siempre hemos usado callbacks en Swift para realizar peticiones asíncronas. Ahora, con la llegada de Async/Await podemos mejorar nuestro código significativamente, haciéndolo más legible, entendible y fácil de mantener.
¿Cómo crear una petición HTTP? Con este listado de videos que te ayudarán a aprender y crear peticiones HTTP. Desde la petición hasta la transformación del JSON a un modelo de tu dominio conformando el protocolo Decodable
URLSession y JSONDecoder en Swift es la combinación perfecta para realizar una petición HTTP y transformar el JSON a un modelo de nuestra app iOS.
Hoy veremos un ejemplo práctico haciendo una petición a una URL y mostrando los datos en SWIFTUI (JSON Swift)