Magia de DispatchQueue.main.asyncAfter: cómo funciona el retardo en Swift 3

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: por qué "esperar" no es tan simple como parece

\n\n

Imagínese: está escribiendo una aplicación para iOS en Swift 3. Necesita mostrar una animación y, después de un par de segundos, ocultarla. O enviar una solicitud al servidor, pero no de inmediato, sino con un retardo. Parecería, ¿qué podría ser más fácil? Tomas y escribes sleep(2) — y listo. Pero si hace eso, su aplicación se convertirá en un bloque de hielo: la interfaz se congelará, el usuario comenzará a tocar la pantalla y, al final, recibirá una reseña negativa y perderá calificación.

\n\n

La pregunta "How to program a delay in Swift 3" ha acumulado miles de visitas en Stack Overflow. Y esto no es casualidad: los principiantes siempre caen en los mismos errores. En este artículo no solo daremos una receta ya preparada. Nos meteremos bajo el capó del mecanismo de retardos, entenderemos cómo funciona DispatchQueue.main.asyncAfter y comprenderemos por qué sleep es malo. Además, les mostraré un truco que les ahorrará horas de depuración.

\n\n

Por qué sleep() es un antipatrón en Swift

\n\n

Aclaremos de inmediato el error más común. Muchos que vienen de otros lenguajes (por ejemplo, de Python con su time.sleep()) intentan hacer esto:

\n\n
sleep(2) // bloqueamos el hilo durante 2 segundos\nprint("Han pasado 2 segundos")
\n\n

Este código bloquea el hilo actual. Si lo llama en el hilo principal (main thread) — todo, la aplicación se "duerme" durante 2 segundos. La interfaz de usuario no se actualiza, los botones no se presionan, las animaciones no funcionan. iOS simplemente matará su aplicación si el hilo principal está bloqueado por demasiado tiempo (generalmente más de 10 segundos).

\n\n

Además, sleep() no tiene en cuenta el estado del RunLoop. Es una llamada de sistema brusca que no permite que el sistema procese eventos correctamente. En Swift 3 (y versiones posteriores), la forma correcta es usar Grand Central Dispatch (GCD).

\n\n

DispatchQueue.main.asyncAfter: la forma correcta de hacer un retardo

\n\n

La forma más común y recomendada de retrasar la ejecución de código en Swift 3 es el método asyncAfter de GCD. Así es como se ve:

\n\n
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {\n    print("Han pasado 2 segundos (modo no bloqueante)")\n}
\n\n

Aquí le decimos al sistema: "Después de 2 segundos desde el momento actual, ejecuta este bloque de código en el hilo principal". El hilo principal permanece libre mientras tanto: las animaciones funcionan, los botones se presionan, el usuario está contento.

\n\n

Pero profundicemos un poco más. ¿Qué es deadline? ¿Por qué .now() + 2.0 y no simplemente 2.0? ¿Y qué sucede "bajo el capó"?

\n\n

Anatomía de asyncAfter: deadline, DispatchTime y DispatchQoS

\n\n

asyncAfter acepta el parámetro deadline de tipo DispatchTime. No es solo un número de milisegundos, sino un punto absoluto en el tiempo, vinculado al reloj del sistema. Cuando escribe .now() + 2.0, GCD calcula: "tiempo actual + 2 segundos" y coloca la tarea en la cola con esta marca.

\n\n

Bajo el capó, GCD utiliza mach_absolute_time() — un temporizador de hardware que no depende de los cambios en la hora del sistema. Es decir, si el usuario atrasa el reloj, su retardo no se romperá (a diferencia de Timer, que puede comportarse de manera impredecible).

\n\n

Un detalle importante: deadline es el tiempo mínimo hasta la ejecución. GCD no garantiza que el código se ejecute exactamente después de 2 segundos. Si el sistema está ocupado (por ejemplo, procesando datos complejos), la ejecución puede retrasarse. Para la mayoría de las tareas de UI, esto no es crítico, pero para temporizadores de alta precisión (por ejemplo, en juegos) es mejor usar CADisplayLink.

\n\n

Otro parámetro es qos (Quality of Service). Puede especificar la prioridad de la tarea:

\n\n
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1.0) {\n    // tarea de alta prioridad\n}
\n\n

Blogs

Book Recommendations