tl; dr il runloop sta eseguendo lo scorrimento in modo da non poter gestire altri eventi, a meno che tu non imposti manualmente il timer in modo che possa accadere anche quando runloop sta gestendo eventi di tocco. Oppure prova una soluzione alternativa e utilizza GCD
Una lettura obbligata per qualsiasi sviluppatore iOS. Molte cose vengono infine eseguite tramite RunLoop.
Derivato dai documenti di Apple .
Cos'è un ciclo di corsa?
Un ciclo di esecuzione è molto simile al suo nome. È un ciclo in cui il thread entra e utilizza per eseguire gestori di eventi in risposta agli eventi in arrivo
Come viene interrotta la consegna degli eventi?
Poiché i timer e altri eventi periodici vengono consegnati quando si esegue il ciclo di esecuzione, aggirare quel ciclo interrompe la consegna di quegli eventi. L'esempio tipico di questo comportamento si verifica ogni volta che si implementa una routine di rilevamento del mouse immettendo un ciclo e richiedendo ripetutamente eventi dall'applicazione. Poiché il codice acquisisce gli eventi direttamente, anziché lasciare che l'applicazione invii normalmente tali eventi, i timer attivi non sarebbero in grado di attivarsi fino a quando la routine di rilevamento del mouse non fosse terminata e avesse restituito il controllo all'applicazione.
Cosa succede se il timer viene attivato quando il ciclo di esecuzione è nel mezzo dell'esecuzione?
Questo accade MOLTE VOLTE, senza che noi ce ne accorgiamo. Voglio dire, impostiamo il timer in modo che si attivi alle 10:10: 10: 00, ma il runloop sta eseguendo un evento che dura fino alle 10:10: 10: 05, quindi il timer viene attivato alle 10:10: 10: 06
Allo stesso modo, se un timer si attiva quando il ciclo di esecuzione è nel mezzo dell'esecuzione di una routine del gestore, il timer attende la volta successiva del ciclo di esecuzione per richiamare la sua routine del gestore. Se il ciclo di esecuzione non è affatto in esecuzione, il timer non si attiva mai.
Lo scorrimento o qualsiasi altra cosa che tiene occupato il ciclo di esecuzione si sposterebbe tutte le volte che il mio timer scatta?
È possibile configurare i timer per generare eventi solo una o più volte. Un timer che si ripete si ripianifica automaticamente in base al tempo di accensione programmato, non al tempo di accensione effettivo. Ad esempio, se un timer è programmato per attivarsi in un determinato momento e successivamente ogni 5 secondi, l'ora di attivazione programmata cadrà sempre sugli intervalli di tempo originali di 5 secondi, anche se l'ora di attivazione effettiva viene ritardata. Se il tempo di accensione viene ritardato così tanto da perdere uno o più dei tempi di accensione programmati, il timer viene attivato solo una volta per il periodo di tempo mancato. Dopo aver sparato per il periodo mancato, il timer viene riprogrammato per il successivo orario di accensione programmato.
Come posso cambiare la modalità di RunLoops?
Non puoi. Il sistema operativo cambia solo per te. ad esempio, quando l'utente tocca, la modalità passa a eventTracking
. Al termine dei tocchi dell'utente, la modalità torna a default
. Se vuoi che qualcosa venga eseguito in una modalità specifica, sta a te assicurarti che accada.
Soluzione:
Quando l'utente sta scorrendo, la modalità Run Loop diventa tracking
. Il RunLoop è progettato per cambiare marcia. Una volta impostata la modalità, viene data eventTracking
priorità (ricorda che abbiamo core CPU limitati) agli eventi di tocco. Questo è un progetto architettonico dei progettisti del sistema operativo .
Per impostazione predefinita, i timer NON sono programmati nella tracking
modalità. Sono in programma:
Crea un timer e lo pianifica sul ciclo di esecuzione corrente nella
modalità predefinita .
Il scheduledTimer
sotto fa questo:
RunLoop.main.add(timer, forMode: .default)
Se vuoi che il timer funzioni durante lo scorrimento, devi eseguire una delle seguenti operazioni:
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self,
selector: #selector(fireTimer), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .tracking)
Oppure fai semplicemente:
RunLoop.main.add(timer, forMode: .common)
In definitiva, eseguire una delle operazioni precedenti significa che il thread non è bloccato dagli eventi di tocco. che è equivalente a:
RunLoop.main.add(timer, forMode: .default)
RunLoop.main.add(timer, forMode: .eventTracking)
RunLoop.main.add(timer, forMode: .modal)
Soluzione alternativa:
Potresti considerare di utilizzare GCD per il tuo timer che ti aiuterà a "proteggere" il tuo codice dai problemi di gestione del ciclo di esecuzione.
Per non ripetere basta usare:
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
}
Per la ripetizione dei timer utilizzare:
Scopri come utilizzare DispatchSourceTimer
Scavando più a fondo da una discussione che ho avuto con Daniel Jalkut:
Domanda: come viene eseguito GCD (thread in background), ad esempio un asyncAfter su un thread in background, al di fuori di RunLoop? La mia comprensione da questo è che tutto deve essere eseguito all'interno di un RunLoop
Non necessariamente - ogni thread ha al massimo un ciclo di esecuzione, ma può avere zero se non c'è motivo di coordinare la "proprietà" di esecuzione del thread.
I thread sono un'affidabilità a livello di sistema operativo che offre al processo la possibilità di suddividere la sua funzionalità in più contesti di esecuzione parallela. I cicli di esecuzione sono un vantaggio a livello di framework che consente di suddividere ulteriormente un singolo thread in modo che possa essere condiviso in modo efficiente da più percorsi di codice.
In genere, se invii qualcosa che viene eseguito su un thread, probabilmente non avrà un ciclo di esecuzione a meno che qualcosa non chiami [NSRunLoop currentRunLoop]
che implicitamente ne creerebbe uno.
In poche parole, le modalità sono fondamentalmente un meccanismo di filtro per ingressi e timer