Un RunLoop es un bucle que coordina la recepción y el procesamiento de eventos entrantes en un hilo específico.
RunLoop está presente en todos los subprocesos, pero de forma predeterminada está en modo de espera y no realiza ningún trabajo.
El desarrollador puede ejecutarlo si es necesario, pero no funcionará automáticamente. Para hacer esto, necesitará escribir código.
En primer lugar, RunLoop está diseñado para gestionar el flujo de tareas entrantes y ejecutarlas en el momento adecuado.
Esto se nota más cuando se trabaja con la interfaz de usuario, por ejemplo, cuando se utiliza UIScrollView.
De forma predeterminada, el RunLoop principal siempre se ejecuta en la aplicación; procesa mensajes del sistema y los transmite a la aplicación. Un ejemplo de dichos mensajes puede ser, por ejemplo, un evento cuando un usuario hace clic en la pantalla.
Los subprocesos auxiliares requieren la autodeterminación de la necesidad de un RunLoop. Si lo necesita, tendrá que configurarlo y ejecutarlo usted mismo. No se recomienda ejecutar RunLoop de forma predeterminada; solo es necesario en los casos en que necesitamos una interacción activa con los subprocesos.
Además, todos los temporizadores de la aplicación se ejecutan en el runloop, por lo que si necesita interactuar con ellos en su aplicación, definitivamente necesita estudiar las características del runloop.
RunLoop es un bucle y tiene varios modos de operación que ayudan al desarrollador a comprender cuándo ejecutar una tarea en particular.
Entonces, RunLoop puede estar en los siguientes modos:
Default
: el modo predeterminado, la transmisión es gratuita y en ella se pueden realizar grandes operaciones de forma segura.
Tracking
: el hilo está ocupado realizando un trabajo importante. En este punto, es mejor no ejecutar ninguna tarea, o al menos ejecutar algunas tareas pequeñas.
Initialization
: este modo se ejecuta una vez durante la inicialización de la transmisión.
EventReceive
: este es un modo interno para recibir eventos del sistema, que generalmente no se usa.
Common
: es un modo de marcador de posición que no tiene importancia práctica.
En el RunLoop principal, estos modos se cambian automáticamente; el desarrollador puede utilizarlos para realizar tareas que requieren mucho tiempo para que el usuario no note que la interfaz se bloquea. Veamos un ejemplo.
La gestión del ciclo de ejecución en otros RunLoop no es completamente automática. Debe escribir código para un hilo que iniciará el ciclo de ejecución en el momento adecuado. Además, debe responder adecuadamente a los eventos y utilizar bucles sin fin para garantizar que el ciclo de ejecución no se detenga.
Tenemos un UIScrollView y necesitamos realizar una gran tarea en el hilo principal para que el usuario no note nada.
Podemos completar la tarea de la forma habitual:
DispatchQueue.main.async { sleep(2) self.tableView.refreshControl?.endRefreshing() }
Pero el resultado va a ser bastante malo. El usuario notará retrasos importantes en la aplicación.
Este efecto negativo se produce debido a que ejecutamos una tarea en el hilo principal sin prestar atención a lo que está sucediendo en él en ese momento.
Debido a esto, comenzamos a realizar nuestra gran tarea en el momento en que el usuario interactúa con la interfaz. Esto, por supuesto, lleva al hecho de que el usuario ve la interfaz bloqueada.
Esto se puede evitar utilizando el mecanismo RunLoop. Implementemos la misma lógica usando esto:
CFRunLoopPerformBlock(CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue) { sleep(2) self.tableView.refreshControl?.endRefreshing() }
Déjame explicarte lo que sucede aquí. La función CFRunLoopPerformBlock agrega código para su ejecución a través de RunLoop. Además del bloque de código en sí, esta función tiene 2 parámetros importantes.
El primero se encarga de seleccionar qué RunLoop debe ejecutar la función. En este ejemplo, se utiliza "principal".
El segundo es responsable del modo en que se completará la tarea.
Hay tres modos posibles en total:
Común: combina el modo predeterminado y el modo de seguimiento.
El resultado de ejecutar el programa con el código anterior será:
Cuando el usuario comienza a interactuar con la interfaz de usuario (UI), el bucle de ejecución principal cambia al modo de "seguimiento" y suspende temporalmente el procesamiento de todos los demás eventos para garantizar la fluidez de la interfaz. Una vez que el usuario deja de interactuar con la interfaz, el ciclo de ejecución vuelve a su modo "predeterminado" y continúa realizando nuestra tarea.
Además de la interfaz de usuario, el bucle también está estrechamente relacionado con el funcionamiento de los temporizadores.
Cualquier temporizador de la aplicación se ejecuta en bucle y hay que tener mucho cuidado de no cometer errores al trabajar con él, especialmente si es responsable de funciones importantes como el procesamiento de pagos.
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in // makeSomething }
El temporizador se inicia en modo predeterminado de forma predeterminada, por lo que puede dejar de funcionar si el usuario se está desplazando por la tabla en ese momento. Esto se debe a que el bucle se encuentra actualmente en modo de seguimiento. Esta es la razón por la que es posible que el código del ejemplo no funcione correctamente. Puede solucionar este problema agregando un temporizador en modo común.
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in // makeSomething } RunLoop.main.add(timer, forMode: .common)
Además, es posible que los temporizadores no se activen a la hora esperada o que no se activen en absoluto. Esto se debe a que RunLoop solo verifica los temporizadores al comienzo de cada ciclo. Si un temporizador se activa después de que RunLoop haya pasado esta etapa, no lo sabremos hasta el comienzo de la siguiente iteración. Al mismo tiempo, cuanto más tiempo se ejecute la tarea en RunLoop, mayor será el retraso.
Para resolver este problema, puede crear un nuevo hilo, iniciar un RunLoop en ese hilo y luego agregar un temporizador al hilo sin agregar ninguna otra tarea; de esta manera, el temporizador funcionará correctamente.
let thread = Thread { let timer = Timer(timeInterval: 1.0, repeats: true) { timer in // makeSomething } RunLoop.current.add(timer, forMode: .default) RunLoop.current.run() } thread.start()
En este artículo, analizamos qué es RunLoop y qué problemas resuelve en las aplicaciones de iOS. Un RunLoop es un bucle que coordina la recepción y el procesamiento de eventos entrantes dentro de un hilo específico y tiene varios modos de operación.
Es especialmente útil cuando se trabaja con la interfaz de usuario (UI) y temporizadores porque tiene la capacidad de ejecutar tareas en el momento adecuado, lo que permite evitar "colgar" la interfaz y garantizar el correcto funcionamiento de los temporizadores.
A pesar de que trabajar con Run Loop requiere codificación adicional, es una inversión que vale la pena y mejora la eficiencia y estabilidad de su aplicación.