Während eine UIScrollView
(oder eine abgeleitete Klasse davon) scrollt, scheint es wie alleNSTimers
ausgeführten Dateien angehalten, bis der ist.
Gibt es eine Möglichkeit, dies zu umgehen? Themen? Eine Prioritätseinstellung? Etwas?
Während eine UIScrollView
(oder eine abgeleitete Klasse davon) scrollt, scheint es wie alleNSTimers
ausgeführten Dateien angehalten, bis der ist.
Gibt es eine Möglichkeit, dies zu umgehen? Themen? Eine Prioritätseinstellung? Etwas?
Antworten:
Eine einfach zu implementierende Lösung ist:
NSTimer *timer = [NSTimer timerWithTimeInterval:...
target:...
selector:....
userInfo:...
repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Für alle, die Swift 3 verwenden
timer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: aSelector,
userInfo: nil,
repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
timer = Timer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true)
als erster Befehl statt als aufzurufen Timer.scheduleTimer()
, da scheduleTimer()
der Runloop mit einem Timer versehen wird und der nächste Aufruf ein weiterer Befehl zum gleichen Runloop ist, jedoch mit einem anderen Modus. Mach nicht zweimal die gleiche Arbeit.
Dies ist die schnelle Version.
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
Sie müssen einen anderen Thread und eine weitere Ausführungsschleife ausführen, wenn Timer beim Scrollen ausgelöst werden sollen. Da Timer als Teil der Ereignisschleife verarbeitet werden, gelangen Sie nie zu den Timern, wenn Sie mit dem Scrollen Ihrer Ansicht beschäftigt sind. Obwohl die Perf / Batterie-Strafe für das Ausführen von Timern auf anderen Threads in diesem Fall möglicherweise nicht sinnvoll ist.
Verwenden Sie für alle Swift 4:
timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .common)
tl; dr Der Runloop führt einen Bildlauf durch, sodass keine Ereignisse mehr verarbeitet werden können - es sei denn, Sie stellen den Timer manuell so ein, dass dies auch passieren kann, wenn der Runloop Berührungsereignisse verarbeitet. Oder versuchen Sie es mit einer alternativen Lösung und verwenden Sie GCD
Ein Muss für jeden iOS-Entwickler. Viele Dinge werden letztendlich über RunLoop ausgeführt.
Abgeleitet von Apples Dokumenten .
Eine Run-Schleife ist ihrem Namen sehr ähnlich. Es ist eine Schleife, in die Ihr Thread eintritt und die zum Ausführen von Ereignishandlern als Reaktion auf eingehende Ereignisse verwendet wird
Da Timer und andere periodische Ereignisse beim Ausführen der Ausführungsschleife übermittelt werden, wird durch die Umgehung dieser Schleife die Übermittlung dieser Ereignisse unterbrochen. Das typische Beispiel für dieses Verhalten tritt immer dann auf, wenn Sie eine Mausverfolgungsroutine implementieren, indem Sie eine Schleife eingeben und wiederholt Ereignisse von der Anwendung anfordern. Da Ihr Code Ereignisse direkt erfasst, anstatt die Anwendung diese Ereignisse normal auslösen zu lassen, können aktive Timer erst ausgelöst werden, nachdem Ihre Mausverfolgungsroutine beendet und die Kontrolle an die Anwendung zurückgegeben wurde.
Dies geschieht VIELE MAL, ohne dass wir es jemals bemerken. Ich meine, wir setzen den Timer auf 10: 10: 10: 00, aber der Runloop führt ein Ereignis aus, das bis 10: 10: 10: 05 dauert, daher wird der Timer 10: 10: 10: 06 ausgelöst
Wenn ein Timer ausgelöst wird, während sich die Ausführungsschleife gerade in der Ausführung einer Handlerroutine befindet, wartet der Timer bis zum nächsten Durchlaufen der Ausführungsschleife, um seine Handlerroutine aufzurufen. Wenn die Run-Schleife überhaupt nicht läuft, wird der Timer nie ausgelöst.
Sie können Timer so konfigurieren, dass Ereignisse nur einmal oder wiederholt generiert werden. Ein sich wiederholender Timer plant sich automatisch neu basierend auf der geplanten Zündzeit und nicht auf der tatsächlichen Zündzeit. Wenn beispielsweise ein Timer zu einem bestimmten Zeitpunkt und danach alle 5 Sekunden ausgelöst werden soll, fällt die geplante Zündzeit immer auf die ursprünglichen Zeitintervalle von 5 Sekunden, selbst wenn sich die tatsächliche Zündzeit verzögert. Wenn die Zündzeit so stark verzögert wird, dass eine oder mehrere der geplanten Zündzeiten verfehlt werden, wird der Timer für den verpassten Zeitraum nur einmal ausgelöst. Nach dem Auslösen für die versäumte Zeit wird der Timer für die nächste geplante Auslösezeit neu geplant.
Das kannst du nicht. Das Betriebssystem ändert sich nur für Sie. Wenn der Benutzer z. B. tippt, wechselt der Modus zu eventTracking
. Wenn die Benutzertipps beendet sind, kehrt der Modus zurück zu default
. Wenn Sie möchten, dass etwas in einem bestimmten Modus ausgeführt wird, müssen Sie sicherstellen, dass dies geschieht.
Wenn der Benutzer einen Bildlauf durchführt, wird der Run-Loop-Modus aktiviert tracking
. Der RunLoop ist zum Schalten ausgelegt. Sobald der Modus eingestellt ist eventTracking
, gibt es Priorität (denken Sie daran, dass wir nur begrenzte CPU-Kerne haben), um Ereignisse zu berühren. Dies ist ein architektonischer Entwurf der OS-Designer .
Standardmäßig sind Timer NICHT für den tracking
Modus geplant. Sie sind geplant am:
Erstellt einen Timer und plant ihn in der aktuellen Ausführungsschleife im Standardmodus .
Das scheduledTimer
Folgende macht das:
RunLoop.main.add(timer, forMode: .default)
Wenn Sie möchten, dass Ihr Timer beim Scrollen funktioniert, müssen Sie Folgendes tun:
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
Oder einfach:
RunLoop.main.add(timer, forMode: .common)
Wenn Sie einen der oben genannten Schritte ausführen, wird Ihr Thread nicht durch Berührungsereignisse blockiert. was äquivalent ist zu:
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.
Sie können GCD für Ihren Timer verwenden, um Ihren Code vor Problemen mit der Verwaltung von Ausführungsschleifen zu schützen.
Für nicht wiederholte verwenden Sie einfach:
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
// your code here
}
Verwenden Sie zum Wiederholen von Timern:
Erfahren Sie, wie Sie DispatchSourceTimer verwenden
Aus einer Diskussion mit Daniel Jalkut vertiefen:
Frage: Wie wird GCD (Hintergrundthreads), z. B. ein asynchroner After in einem Hintergrundthread, außerhalb von RunLoop ausgeführt? Mein Verständnis davon ist, dass alles innerhalb einer RunLoop ausgeführt werden soll
Nicht unbedingt - jeder Thread hat höchstens eine Ausführungsschleife, kann aber Null haben, wenn es keinen Grund gibt, die Ausführung "Besitz" des Threads zu koordinieren.
Threads sind ein Vorteil auf Betriebssystemebene, mit dem Ihr Prozess seine Funktionalität auf mehrere parallele Ausführungskontexte aufteilen kann. Ausführungsschleifen sind eine Möglichkeit auf Framework-Ebene, mit der Sie einen einzelnen Thread weiter aufteilen können, damit er von mehreren Codepfaden effizient gemeinsam genutzt werden kann.
Wenn Sie etwas versenden, das in einem Thread ausgeführt wird, hat es normalerweise keinen Runloop, es sei denn, es wird etwas aufgerufen, [NSRunLoop currentRunLoop]
das implizit einen erstellen würde.
Kurz gesagt, Modi sind im Grunde genommen ein Filtermechanismus für Eingänge und Timer