Programación asíncrona en Swift: guía completa en la práctica
La programación asíncrona es uno de los conceptos clave del desarrollo moderno para iOS y macOS. En Swift, a partir de la versión 5.5, apareció un potente sistema de Structured Concurrency, que hizo que trabajar con asincronía fuera simple y seguro. En este artículo analizaremos las herramientas principales: async/await, Task, Actor y Sendable, y mostraremos su aplicación en ejemplos reales.
1. Fundamentos de async/await: cómo dejar de escribir closures y empezar a vivir
Antes de la llegada de async/await, el código asíncrono en Swift se escribía con closures y delegados, lo que a menudo llevaba al «infierno de los callbacks». El enfoque moderno permite escribir código asíncrono de forma tan lineal como el síncrono.
Se puede declarar una función asíncrona con la palabra clave async. La llamada a dicha función requiere un contexto que soporte asincronía y la palabra clave await.
// Ejemplo de función asíncrona para cargar datosfunc fetchUserData(userID: String) async throws -> User { let url = URL(string: "https://api.example.com/users/\\(userID)")! let (data, response) = try await URLSession.shared.data(from: url) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw URLError(.badServerResponse) } let user = try JSONDecoder().decode(User.self, from: data) return user}
// UsoTask { do { let user = try await fetchUserData(userID: "123") print("User name: \\(user.name)") } catch { print("Error: \\(error)") }}Características clave:
async— marcador de función asíncronaawait— punto de suspensión de la ejecución- La función puede ser tanto
asynccomothrowsal mismo tiempo - El hilo no se bloquea — durante la espera puede realizar otro trabajo
2. Task y TaskGroup: gestión de tareas paralelas
Task es la unidad básica de trabajo asíncrono. Crea una nueva tarea asíncrona que se ejecuta en el grupo global de hilos. TaskGroup permite lanzar varias tareas en paralelo y recopilar sus resultados.
Creación y cancelación de Task
// Creación de una tarealet task = Task { let result = await performLongOperation() return result}
// Obtención del resultadolet value = await task.value
// Cancelación de la tareatask.cancel()
// Verificación de cancelación dentro de la tareafunc performLongOperation() async -> String { // Verificamos si la tarea no ha sido cancelada guard !Task.isCancelled else { return "Cancelled" } // Emulamos un trabajo largo try? await Task.sleep(nanoseconds: 2_000_000_000) return "Completed"}Procesamiento paralelo con TaskGroup
// Carga de varias imágenes en paralelofunc loadImages(urls: [URL]) async throws -> [UIImage] { return try await withThrowingTaskGroup(of: UIImage.self) { group in var images: [UIImage] = [] images.reserveCapacity(urls.count) for url in urls { group.addTask { let (data, _) = try await URLSession.shared.data(from: url) guard let image = UIImage(data: data) else { throw URLError(.cannotDecodeContentData) } return image } } // Recopilamos los resultados a medida que estén listos for try await image in group { images.append(image) } return images }}Importante: TaskGroup gestiona automáticamente el ciclo de vida de las tareas hijas