Mientras un UIScrollView
(o una clase derivada del mismo) se desplaza, parece que todos los NSTimers
que se están ejecutando se detienen hasta que finaliza el desplazamiento.
¿Hay alguna forma de evitar esto? ¿Hilos? ¿Un establecimiento de prioridades? ¿Cualquier cosa?
ios
uiscrollview
nstimer
mcccclean
fuente
fuente
Respuestas:
Una solución fácil y sencilla de implementar es hacer:
NSTimer *timer = [NSTimer timerWithTimeInterval:... target:... selector:.... userInfo:... repeats:...]; [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
fuente
Para cualquiera que use Swift 3
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true) RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
fuente
timer = Timer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true)
como el primer comando en lugar deTimer.scheduleTimer()
, porquescheduleTimer()
agrega un temporizador a runloop, y la siguiente llamada es otra adición al mismo runloop pero con un modo diferente. No hagas el mismo trabajo dos veces.Sí, Paul tiene razón, este es un problema de ciclo de ejecución. Específicamente, debe hacer uso del método NSRunLoop:
- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
fuente
Esta es la versión rápida.
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true) NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
fuente
Debe ejecutar otro hilo y otro ciclo de ejecución si desea que los temporizadores se activen mientras se desplaza; Dado que los temporizadores se procesan como parte del ciclo de eventos, si está ocupado procesando el desplazamiento de su vista, nunca llega a los temporizadores. Aunque la penalización de rendimiento / batería de ejecutar temporizadores en otros subprocesos podría no valer la pena manejar este caso.
fuente
para cualquiera que use Swift 4:
timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true) RunLoop.main.add(timer, forMode: .common)
fuente
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?
¿Cómo se interrumpe la entrega de eventos?
¿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
¿El desplazamiento o cualquier cosa que mantenga ocupado el runloop cambiará todas las veces que mi temporizador se disparará?
¿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 adefault
. 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á configuradoeventTracking
, 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: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) // sets it on `.default` mode RunLoop.main.add(timer, forMode: .tracking) // AND Do this
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) // This is more of a macOS thing for when you have a modal panel showing.
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) { // your code here }
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.
fuente