Programación Asíncrona en Swift

En esta publicación, usaré las siguientes librerías de terceros para completar el proyecto:
- Alamofire: Un framework de redes HTTP en Swift.
- SwiftyJSON: Para procesar datos JSON.
- SwiftGifOrigin: Una extensión de UIImage para mostrar archivos Gif.
- Bolts-Swift: Fue diseñado por Parse y Facebook, lo uso para crear métodos asíncronos.
- PromiseKit: Un framework que nos ayuda a simplificar la programación asíncrona.
- APIs de Giphy para buscar y descargar imágenes gif.
Comenzando
Los métodos asíncronos, (Async para abreviar), son métodos que no retornan resultados inmediatamente como la mayoría de los métodos, los métodos async toman algo de tiempo para producir resultados.
A menudo uso callbacks para manejar métodos asíncronos como escanear dispositivos Bluetooth o recuperar algunos recursos de internet. De hecho, callback es una técnica de programación mala. Callback hará que nuestro código sea difícil de leer, difícil de depurar y tome mucho más tiempo para mantener después. Al final, nuestro código se convertirá en algo que llamamos el infierno de callbacks.
En esta publicación, crearé un proyecto usando una técnica a la vez para explicar por qué dije que callback es malo.
Primero, ve y crea un proyecto, nómbralo como quieras, luego instala estos frameworks de Pod en tu proyecto. También necesitas editar la clave NSAllowsArbitraryLoads a YES en el diccionario NSAppTransportSecurity en el archivo info.plist para especificar qué dominios están exceptuados de las reglas que defines para App Transport Security. En nuestro caso, este es el dominio de giphy.
Permitir solicitudes HTTP solo para el dominio de giphy
1 | <key>NSAppTransportSecurity</key> |
O permitir solicitudes HTTP para todos los dominios, no es una buena idea.
1 | <key>NSAppTransportSecurity</key> |
Creemos una clase llamada ImageLoader. Esta clase contiene dos métodos que nos ayudan a buscar y descargar imágenes gif del servidor de Giphy.
1 | // |
La primera versión: Usando callback
Primero, necesitamos definir dos callbacks, que se pasarán a los métodos fetchImage y downloadImage.
1 | public typealias FetchImageBlock = (URL?, Error?) -> Void |
Luego, implementamos estos dos métodos:
fetchImagetoma una palabra clave y un callback como parámetros, envía una solicitud al servidor de Giphy para consultar todas las imágenes que coinciden con la palabra clave, obtiene la primera y finalmente retorna la url de descarga a través del callback.downloadImagetoma una url y un callback como parámetros, luego usa el frameworkAlamofirepara descargar la imagen. Finalmente, retorna la url de destino, donde se guarda la imagen, a través del callback.
1 | func fetchImage(keyword: String, callback: @escaping FetchImageBlock) { |
1 | func downloadImage(url: URL, callback: @escaping DownloadImageBlock) { |
Dentro del controlador de vista principal, definamos un método llamado searchImageWithKeyword. Este método toma una palabra clave como parámetro, luego pasa el parámetro al método fetchImage de una instancia de la clase ImageLoader. También necesitamos pasar un callback para manejar los resultados.
Dentro del callback de fetchImage, verifiquemos si hay algún error. Si lo hay, entonces dejamos de llamar al siguiente método, downloadImage. De lo contrario, llamamos al downloadImage del objeto imageLoader. Luego pasamos la url y un callback como parámetros.
Dentro del callback de downloadImage, verifiquemos si hay algún error. Si lo hay, entonces dejamos de llamar al siguiente. De lo contrario, actualizamos la vista de imagen en la vista principal llamando al método updateImageAtURL.
1 | func searchImageWithKeyword(keyword: String) { |
1 | func updateImageAtURL(url: URL) { |
Como puedes ver, el searchImageWithKeyword es bastante complejo con muchas declaraciones if y else dentro del método. Tenemos que verificar errores en muchas líneas de código. ¿Imaginas cuán complejo sería si tuviéramos más de tres métodos dentro de sí mismo?

Compila y ejecuta el proyecto. Ingresa una palabra clave que quieras buscar en el servidor de Giphy, presiona el botón de búsqueda y verás el primer resultado.
Una mejor solución: Usando Bolts
Bolts es un framework que fue diseñado por Parse y Facebook, lo uso para crear métodos asíncronos, sin usar callback. El framework Bolts nos permite escribir código como una serie de acciones basadas en eventos.
1 | func fetchImage(keyword: String) -> Task<URL>! { |
1 | func downloadImage(url: URL) -> Task<URL>! { |
Veamos cuán simple sería el searchImageWithKeyword usando Bolts.
1 | func searchImageWithKeyword(keyword: String) { |
Compila y ejecuta el proyecto, nada cambió. Pero el código es más legible que el primero, ¿no? Reunimos todos los errores en un solo lugar, también separamos el manejo de errores y el código de éxito.
Una solución mucho mejor: Usando PromiseKit
Una cosa que no me gusta del framework Bolts es la falta de documentación y proyectos de ejemplo. Cuando usé por primera vez el framework Bolts, fue muy difícil acostumbrarme a las APIs del objeto Task.
En la conferencia Swift Summit 2017, hubo un ponente que presentó un Framework para manejar métodos async, PromiseKit. Después de la conferencia, reemplacé el código que usaba el framework Bolts por PromiseKit en los proyectos de mi empresa. Me di cuenta de que mi código ahora es más legible. Creo que la escritura de PromiseKit será más familiar para los desarrolladores que la escritura de Bolts, especialmente para aquellos que han trabajado con Javascript como yo.
Un método async creado usando PromiseKit retorna una nueva Promise genérica, que es la clase principal proporcionada por PromiseKit. Su constructor toma un bloque de ejecución simple con dos parámetros:
- fulfill: Una función a llamar cuando el valor deseado está listo para cumplir la promesa.
- reject: Una función a llamar si hay un error.
Apliquemos PromiseKit a nuestro proyecto
1 | func fetchImage(keyword: String) -> Promise<URL> { |
1 | func downloadImage(url: URL) -> Promise<URL> { |
Y el resultado final, ¡qué código tan hermoso! :))
1 | func searchImageWithKeyword(keyword: String) { |
Una característica que encuentro muy interesante en ambos frameworks, Bolts y PromiseKit, es que permiten que nuestro código se ejecute en un hilo dado (hilo principal o hilo de fondo). Esta es una gran característica ya que la mayor parte del trabajo realizado en el controlador de vista ha sido para actualizar la UI. A veces, las tareas de larga duración se manejan mejor en un hilo de fondo, para no bloquear la UI. Para más detalles sobre esta característica de Thread, por favor consulta sus documentos: #Threading
Conclusión
Ya que estoy trabajando en CoreBluetooth, a menudo tengo que trabajar con métodos async. Demasiados callbacks hacen que mi proyecto sea más difícil de entender y difícil de depurar si ocurren errores. Promise hace que mi código se convierta en una chica más hermosa ;).
Puedes descargar el proyecto de ejemplo completamente terminado aquí.
Siéntete libre de dejar tus comentarios en mi publicación.