Introducción a la programación asíncrona en Swift
\n\nLas aplicaciones móviles modernas rara vez prescinden de solicitudes de red, trabajo con bases de datos o procesamiento de archivos grandes. Todas estas operaciones requieren tiempo, y si se ejecutan en el hilo principal, la interfaz de la aplicación se "congelará" y el usuario tendrá una mala experiencia. Aquí es donde la programación asíncrona viene al rescate.
\n\nAntes de la llegada de Swift 5.5, los desarrolladores usaban closures (completion handlers) o bibliotecas de terceros (Combine, RxSwift), lo que a menudo llevaba al "infierno de los callbacks" y a un código difícil de leer. Con el lanzamiento del nuevo modelo Swift Concurrency (async/await), escribir código asíncrono se ha vuelto tan simple y lineal como el código síncrono.
\n\nEn esta guía, analizaremos los conceptos clave de la programación asíncrona en Swift con ejemplos prácticos. Aprenderás a usar async/await, Task, Actor y otras herramientas para crear aplicaciones rápidas y receptivas para iOS y macOS.
Conceptos principales de Swift Concurrency
\n\n1. Async y Await: la base de la asincronía
\n\nLas palabras clave async y await son el corazón del nuevo modelo. async declara una función como asíncrona, y await pausa la ejecución hasta obtener el resultado, sin bloquear el hilo.
Veamos un ejemplo simple: cargar datos desde la red.
\n\nimport Foundation\n\n// Declaramos una función asíncrona\nfunc fetchUserData() async throws -> String {\n let url = URL(string: "https://api.example.com/user/1")!\n let (data, _) = try await URLSession.shared.data(from: url)\n return String(data: data, encoding: .utf8) ?? "Error"\n}\n\n// Llamada a la función asíncrona\nTask {\n do {\n let userData = try await fetchUserData()\n print("Datos del usuario: \\(userData)")\n } catch {\n print("Error de carga: \\(error.localizedDescription)")\n }\n}\n\nObserva: try await significa que esperamos un resultado que puede lanzar un error. El código se ve como síncrono normal, pero no bloquea el hilo principal.
2. Task: punto de entrada al mundo asíncrono
\n\nLas funciones asíncronas solo se pueden llamar desde otro contexto asíncrono. Para iniciar una operación asíncrona desde código síncrono (por ejemplo, desde viewDidLoad()) se usa Task.
class ViewController: UIViewController {\n \n override func viewDidLoad() {\n super.viewDidLoad()\n \n // Iniciamos una tarea asíncrona\n Task {\n let data = await loadData()\n updateUI(with: data)\n }\n }\n \n func loadData() async -> String {\n // Simulación de carga larga\n try? await Task.sleep(nanoseconds: 2_000_000_000)\n return "Datos cargados"\n }\n \n func updateUI(with data: String) {\n print("Actualizando UI: \\(data)")\n }\n}\n\nImportante: Task crea una nueva tarea asíncrona que se ejecuta en un hilo en segundo plano. Para actualizar la UI, asegúrate de volver al hilo principal; por defecto, await lo hace automáticamente.
3. Actor: protección contra condiciones de carrera
\n\nAl trabajar con multitarea, a menudo surge el problema de condición de carrera (data race), cuando varios hilos modifican una misma variable simultáneamente. Swift ofrece una solución elegante: Actor.
Actor es un tipo por referencia que garantiza que solo un hilo puede acceder a su estado mutable en cualquier momento.
actor Counter {\n private var value = 0\n \n func increment() {\n value += 1\n }\n \n func getValue() -> Int {\n return value\n }\n}\n\n// Uso\nlet counter = Counter