Profile Software Services

Concurrencia en Swift: camino hacia la eficiencia

La gestión eficiente de la concurrencia es un aspecto crucial en el desarrollo de aplicaciones. En el ecosistema de iOS y macOS, Swift se ha convertido en el lenguaje preferido por muchos desarrolladores debido a su poder y versatilidad. Sin embargo, para aprovechar al máximo su potencial, es fundamental comprender y dominar las herramientas que ofrece para manejar la concurrencia de manera efectiva. En este post, veremos los fundamentos de la concurrencia en Swift, desde los desafíos que enfrentamos al desarrollar aplicaciones hasta las herramientas y técnicas más avanzadas disponibles en el lenguaje. Desde la evolución de Grand Central Dispatch (GCD) hasta la introducción de async/await, nos adentraremos en un mundo de hilos, colas y bloques para descubrir cómo Swift nos permite escribir aplicaciones más rápidas, reactivas y robustas.

¡Empecemos!

Origen de Swift

Cuando Swift fue introducido por primera vez por Apple en 2014, tuvo como objetivo satisfacer todas las demandas que los ingenieros de software tenían para los lenguajes de programación modernos. Chris Lattner, quien diseñó Swift en Apple, tenía la meta de crear un lenguaje que pudiera ser utilizado tanto para enseñar programación como para construir software para sistemas operativos.

Una de las necesidades más demandadas por los desarrolladores era una forma de gestionar la concurrencia de forma nativa. Y por fin, desde 2021, fue incluído en el lenguaje.

Desafíos de la concurrencia

En la mayoría de los sistemas informáticos modernos, el hilo principal se utiliza para renderizar y manejar la interfaz de usuario y las interacciones del usuario. A menudo, se destaca a los desarrolladores de iOS que nunca deben bloquear el hilo principal.

Tareas de larga duración como realizar una solicitud de red, interactuar con un sistema de archivos o consultar una base de datos pueden bloquear el hilo principal, haciendo que la interfaz de usuario de una aplicación se congele. Afortunadamente, Apple ha proporcionado varias herramientas diferentes que podemos usar para evitar bloquear la interfaz de usuario de una aplicación.

GCD: herramienta la gestión de tareas

Las mejoras en frameworks como GCD y libdispatch han facilitado mucho la programación concurrente.

La práctica actual recomendada para dispositivos iOS es descargar cualquier tarea que bloquee el hilo principal a un hilo de fondo o una cola. Una vez que se completa la tarea, los resultados generalmente se manejan en un bloque o cierre trailing.

Antes del lanzamiento de GCD, Apple proporcionó APIs que usaban delegación para descargar tareas. Primero, un desarrollador tenía que ejecutar un hilo separado a un objeto delegado, que llamaba a un método en la clase llamadora para manejar la finalización de la tarea.

Aunque descargar una tarea funciona, leer este tipo de código puede ser difícil y cualquier error puede permitir la introducción de nuevos tipos de errores. Por lo tanto, en 2017, Chris Lattner escribió su Manifiesto de Concurrencia de Swift, que expresaba sus ideas sobre cómo agregar concurrencia a Swift utilizando async/await.

GCD, introducido por primera vez en 2009, es el método de Apple para gestionar la paralelización de tareas a través de un grupo de hilos administrados en los sistemas operativos de Apple.

Implementación práctica con GCD

La implementación de GCD se originó como una biblioteca C, lo que permitía a los desarrolladores usarla con C, C++ y Objective-C. Después de la introducción de Swift, se creó un envoltorio Swift para GCD para los desarrolladores que usaban el nuevo lenguaje de Apple.

GCD también se ha portado a libdispatch, que se utiliza en otro software de código abierto. El servidor web Apache ha incorporado esta biblioteca para el multiprocesamiento.

¡Veamos GCD en acción! Usaremos GCD para asignar trabajo a otra cola de despacho. En el fragmento de código a continuación, una función asigna parte de su trabajo a una tarea asíncrona:

concurrencia en Swift GCD

La clase DispatchQueue proporciona métodos y propiedades que permiten a los desarrolladores ejecutar código en un cierre trailing. Un escenario común es ejecutar una tarea de larga duración en un cierre trailing que produce algún tipo de resultado, y luego devolver ese resultado al hilo principal.

En el fragmento de código a continuación, DispatchQueue está haciendo algo de trabajo antes de devolver un resultado al hilo principal:

Un escenario más común sería realizar una llamada de red usando NSURLSession, manejar los resultados en un cierre trailing y luego volver al hilo principal:

Aunque el ejemplo anterior se compilará y ejecutará, hay varios errores. En primer lugar, no estamos usando manipuladores de finalización en todas partes donde la función puede salir. También es más difícil de leer al escribir código de manera síncrona.

Para mejorar el código anterior, usaremos async y await.

La llegada de async/await

Cuando se lanzan iOS 15 y macOS 12 en otoño de 2021 se incluyó la posibilidad de usar async/await.

Estas dos palabras clave están convirtiéndose en la práctica recomendada para que los desarrolladores escriban código concurrente en lenguajes de programación modernos. Echemos un vistazo a la función anterior goGrabSomething, reescrita utilizando la nueva sintaxis async/await:

En el ejemplo anterior, agregamos la palabra clave async antes de throws y después del nombre de la función. Si nuestra función no lanzara excepciones, async iría antes de ->.

Puedes cambiar la firma de la función para que ya no requiera una finalización. Ahora podemos devolver el objeto que se ha decodificado de nuestra llamada API.

Cada vez que usamos un await en el cuerpo de nuestra función, se crea una continuación. Si el sistema tiene que esperar cuando procesa nuestra función, puede suspender nuestra función hasta que esté listo para volver desde su estado suspendido.

Si intentamos llamar a la función goGrabSomething desde un código síncrono, fallará. ¡Swift proporciona una buena solución para ese caso de uso! Podemos usar un cierre asíncrono en nuestro código síncrono para llamar a nuestras funciones asíncronas:

Conclusión de concurrencia en Swift

Ahora, Swift tiene su propio sistema para gestionar la concurrencia y la paralelización. Al aprovechar estas nuevas palabras clave, podemos aprovechar las nuevas funciones de concurrencia en el sistema.

El resultado final es que podemos escribir una función que sea más fácil de leer y contenga menos código.

Async/await en Swift simplifica en gran medida cómo escribimos código concurrente en aplicaciones iOS. Es algo necesario de adoptar de cara al futuro en Swift 6 que deprecará el uso de GCD en favor de async/await ¡Por lo que es mejor estar preparados!

Salir de la versión móvil