tl; dr, el runloop está desplazándose, por lo que no puede manejar más eventos, a menos que configure manualmente el temporizador para que también pueda suceder cuando runloop está manejando eventos táctiles. O prueba una solución alternativa y usa GCD
Una lectura obligada para cualquier desarrollador de iOS. En última instancia, muchas cosas se ejecutan a través de RunLoop.
Derivado de los documentos de Apple .
¿Qué es un Run Loop?
Un bucle de ejecución es muy parecido al sonido de su nombre. Es un bucle que ingresa su hilo y usa para ejecutar controladores de eventos en respuesta a eventos entrantes
¿Cómo se interrumpe la entrega de eventos?
Debido a que los temporizadores y otros eventos periódicos se entregan cuando ejecuta el ciclo de ejecución, eludir ese ciclo interrumpe la entrega de esos eventos. El ejemplo típico de este comportamiento ocurre siempre que implementa una rutina de seguimiento del mouse ingresando un bucle y solicitando repetidamente eventos de la aplicación. Debido a que su código captura eventos directamente, en lugar de permitir que la aplicación distribuya esos eventos normalmente, los temporizadores activos no podrían dispararse hasta después de que su rutina de seguimiento del mouse salga y devuelva el control a la aplicación.
¿Qué sucede si el temporizador se activa cuando el bucle de ejecución está en medio de la ejecución?
Esto sucede MUCHAS VECES, sin que nos demos cuenta. Quiero decir, configuramos el temporizador para que se active a las 10: 10: 10: 00, pero el runloop está ejecutando un evento que tarda hasta las 10: 10: 10: 05, por lo que el temporizador se dispara a las 10: 10: 10: 06
De manera similar, si un temporizador se dispara cuando el ciclo de ejecución está en medio de la ejecución de una rutina de controlador, el temporizador espera hasta la próxima vez a través del ciclo de ejecución para invocar su rutina de controlador. Si el ciclo de ejecución no se está ejecutando, el temporizador nunca se dispara.
¿El desplazamiento o cualquier cosa que mantenga ocupado el runloop cambiará todas las veces que mi temporizador se disparará?
Puede configurar temporizadores para generar eventos solo una vez o repetidamente. Un temporizador de repetición se reprograma automáticamente según el tiempo de disparo programado, no el tiempo de disparo real. Por ejemplo, si un temporizador está programado para disparar a una hora en particular y cada 5 segundos después de eso, el tiempo de disparo programado siempre caerá en los intervalos de tiempo originales de 5 segundos, incluso si el tiempo de disparo real se retrasa. Si el tiempo de disparo se retrasa tanto que pierde uno o más de los tiempos de disparo programados, el temporizador se dispara solo una vez durante el período de tiempo perdido. Después de disparar durante el período perdido, el temporizador se reprograma para el siguiente tiempo de disparo programado.
¿Cómo puedo cambiar el modo de RunLoops?
No puedes. El sistema operativo simplemente cambia por usted. por ejemplo, cuando el usuario toca, el modo cambia a eventTracking
. Cuando los toques del usuario terminan, el modo vuelve a default
. Si desea que algo se ejecute en un modo específico, depende de usted asegurarse de que eso suceda.
Solución:
Cuando el usuario se desplaza, el modo Run Loop se convierte en tracking
. El RunLoop está diseñado para cambiar de marcha. Una vez que el modo está configurado eventTracking
, da prioridad (recuerde que tenemos núcleos de CPU limitados) para tocar eventos. Este es un diseño arquitectónico de los diseñadores de SO .
Por defecto, los temporizadores NO están programados en el tracking
modo. Están programados para:
Crea un temporizador y lo programa en el ciclo de ejecución actual en el
modo predeterminado .
El de scheduledTimer
abajo hace esto:
RunLoop.main.add(timer, forMode: .default)
Si desea que su temporizador funcione cuando se desplaza, debe hacer lo siguiente:
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self,
selector: #selector(fireTimer), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .tracking)
O simplemente haz:
RunLoop.main.add(timer, forMode: .common)
En última instancia, hacer uno de los anteriores significa que su hilo no está bloqueado por eventos táctiles. que es equivalente a:
RunLoop.main.add(timer, forMode: .default)
RunLoop.main.add(timer, forMode: .eventTracking)
RunLoop.main.add(timer, forMode: .modal)
Solución alternativa:
Puede considerar usar GCD para su temporizador, lo que lo ayudará a "proteger" su código de problemas de administración de bucle de ejecución.
Para no repetir, solo use:
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
}
Para temporizadores repetidos use:
Vea cómo usar DispatchSourceTimer
Profundizando en una discusión que tuve con Daniel Jalkut:
Pregunta: ¿cómo se ejecuta GCD (subprocesos en segundo plano), por ejemplo, un asyncAfter en un subproceso en segundo plano, fuera de RunLoop? Tengo entendido de esto que todo se debe ejecutar dentro de un RunLoop
No necesariamente, cada hilo tiene como máximo un ciclo de ejecución, pero puede tener cero si no hay razón para coordinar la "propiedad" de ejecución del hilo.
Los subprocesos son una prestación de nivel de sistema operativo que le da a su proceso la capacidad de dividir su funcionalidad en múltiples contextos de ejecución paralela. Los bucles de ejecución son una prestación de nivel de marco que le permite dividir aún más un solo hilo para que pueda ser compartido de manera eficiente por múltiples rutas de código.
Por lo general, si envía algo que se ejecuta en un hilo, probablemente no tendrá un runloop a menos que algo llame [NSRunLoop currentRunLoop]
lo que crearía uno implícitamente.
En pocas palabras, los modos son básicamente un mecanismo de filtro para entradas y temporizadores.