Introducción: ¿por qué tu iPhone no se congela mientras desplazas el feed?
\n\n¿Alguna vez te has preguntado por qué las aplicaciones modernas de iOS no se ralentizan, incluso cuando descargan decenas de imágenes, procesan toques y escriben datos en la base de datos al mismo tiempo? La respuesta es simple: concurrencia. En el mundo de Swift (y no solo) este es uno de los temas más candentes. Y si escribes en Swift, pero todavía te confundes con hilos, gorutinas y el moderno async/await — bienvenido al club. Hoy lo desglosaremos todo para que tu código vuele, no se arrastre.
En este artículo no solo enumeraremos definiciones secas. Veremos ejemplos reales, compararemos enfoques y responderemos a la pregunta principal: ¿qué elegir para tu proyecto — el viejo y confiable GCD o el nuevo Swift Concurrency?
\n\n\n\nHilos: el fundamento sobre el que todo se sostiene
\n\nEmpecemos por la base. Un hilo (Thread) es la unidad de procesamiento más pequeña que puede ejecutar el sistema operativo. Imagina una cinta transportadora: un hilo es una línea por la que se mueven las tareas. Si tienes un núcleo de procesador, los hilos se ejecutarán uno tras otro, pero cambiando muy rápidamente, creando la ilusión de paralelismo. En procesadores multinúcleo, los hilos pueden ejecutarse realmente en paralelo.
\n\nEn Swift, durante mucho tiempo la herramienta principal para trabajar con hilos fue Grand Central Dispatch (GCD). Simplemente creabas una cola y enviabas un cierre (closure) a ella:
\n\nDispatchQueue.global(qos: .background).async {\n // Trabajo pesado en segundo plano\n let result = heavyComputation()\n DispatchQueue.main.async {\n // Actualización de la UI en el hilo principal\n self.label.text = result\n }\n}\n\nTodo es simple, ¿verdad? Pero GCD tiene matices:
\n- \n
- Tú mismo eres responsable de volver al hilo principal (es fácil olvidarlo y obtener un crash). \n
- No hay soporte integrado para cancelar tareas (hay que escribir banderas manualmente). \n
- Es difícil manejar cadenas de operaciones dependientes sin anidar cierres (el llamado «callback hell»). \n
Sin embargo, GCD sigue vivo y se usa en proyectos legacy. Pero el Swift moderno ofrece algo mucho más elegante.
\n\n¿Gorutinas en Swift? No, esto no es Go
\n\nCuando se habla de gorutinas, generalmente se piensa en Go. En Swift no hay un análogo directo de las gorutinas, pero existen tareas ligeras (tasks) que se comportan de manera muy similar. No están vinculadas a un hilo específico — el sistema decide por sí mismo en qué hilo ejecutar la tarea y puede cambiarlas entre hilos sin crear nuevos para cada llamada.
\n\nA diferencia de los hilos, que «pesan» alrededor de 8 KB de pila cada uno, las tareas de Swift Concurrency prácticamente no pesan nada. Puedes ejecutar miles de tareas sin riesgo de desbordar la memoria. Esta es la famosa «concurrencia sin dolor».
\n\nMira cómo se ve:
\n\nTask {\n let image = await downloadImage(from: url)\n await MainActor.run {\n self.imageView.image = image\n }\n}\n\n¿Notaste? Nada de DispatchQueue.main.async. Simplemente await MainActor.run. Limpio, claro, seguro.
async/await: la revolución que ocurrió
\n\nCon el lanzamiento de Swift 5.5, llegó el soporte integrado para async/await. Esto no es solo azúcar sintáctico — es un cambio de paradigma. Ahora puedes escribir código asíncrono como si fuera síncrono, pero sin bloquear el hilo.
\n\nDeclarar una función asíncrona es simple:
\n\nfunc fetchUserData() async throws -> User {\n let url = URL(string: "https://api.example.com/user")!\n let (data, _) = try await URLSession.shared.data(from: url)\n return try JSONDecoder().decode(User.self, from: data)\n}\n\nY llamarla es aún más simple:
\n\nTask {\n do {\n let user = try await fetchUserData()\n print("User nam