Stellen Sie sich vor, Sie arbeiten an einer Anwendung, in der Sie regelmäßig einige Aktionen ausführen müssen. Genau dafür verwendet Swift die
Timer- Klasse.
Der Timer wird verwendet, um Aktionen in der Anwendung zu planen. Dies kann eine einmalige Aktion oder ein sich wiederholender Vorgang sein.
In diesem Handbuch erfahren Sie, wie der Timer in iOS funktioniert, wie er die Reaktionsfähigkeit der Benutzeroberfläche beeinflusst, wie der Batterieverbrauch bei Verwendung eines Timers optimiert wird und wie
CADisplayLink für Animationen verwendet wird.
Als Teststandort verwenden wir die Anwendung - einen primitiven Taskplaner.
Erste Schritte
Laden Sie das
Quellprojekt herunter
. Öffnen Sie es in Xcode, sehen Sie sich seine Struktur an, kompilieren Sie es und führen Sie es aus. Sie sehen den einfachsten Taskplaner:

Fügen Sie eine neue Aufgabe hinzu. Tippen Sie auf das Symbol +, geben Sie den Namen der Aufgabe ein und tippen Sie auf OK.
Hinzugefügte Aufgaben haben einen Zeitstempel. Die neue Aufgabe, die Sie gerade erstellt haben, wird mit null Sekunden markiert. Wie Sie sehen können, steigt dieser Wert nicht an.
Jede Aufgabe kann als erledigt markiert werden. Tippen Sie auf die Aufgabe. Der Aufgabenname wird durchgestrichen und als erledigt markiert.
Erstellen Sie unseren ersten Timer
Lassen Sie uns den Haupttimer unserer Anwendung erstellen. Die
Timer- Klasse, auch als
NSTimer bekannt, ist eine bequeme Möglichkeit, eine Aktion für einen bestimmten Moment zu planen, sowohl einzeln als auch periodisch.
Öffnen Sie
TaskListViewController.swift und fügen Sie diese Variable zu
TaskListViewController hinzu :
var timer: Timer?
Fügen Sie dort die Erweiterung hinzu:
Und fügen Sie diesen Code in die Erweiterung ein:
@objc func updateTimer() {
Bei dieser Methode haben wir:
- Überprüfen Sie, ob die Aufgabentabelle sichtbare Zeilen enthält.
- Rufen Sie updateTime für jede sichtbare Zelle auf. Diese Methode aktualisiert den Zeitstempel in der Zelle (siehe TaskTableViewCell.swift ).
Fügen Sie dann diesen Code zur Erweiterung hinzu:
func createTimer() {
Hier sind wir:
- Überprüfen Sie, ob der Timer eine Instanz der Timer- Klasse enthält.
- Wenn nicht, erstellen Sie einen Timer, der updateTimer () jede Sekunde aufruft.
Dann müssen wir einen Timer erstellen, sobald der Benutzer die erste Aufgabe hinzufügt. Fügen Sie
createTimer () ganz am Anfang der Methode
presentAlertController (_ :) hinzu .
Starten Sie die Anwendung und erstellen Sie einige neue Aufgaben. Sie werden sehen, dass sich der Zeitstempel für jede Aufgabe jede Sekunde ändert.

Timer-Toleranz hinzufügen
Das Erhöhen der Anzahl der Timer führt zu einer schlechteren Reaktionsfähigkeit der Benutzeroberfläche und einem höheren Batterieverbrauch. Jeder Timer versucht, genau zu dem ihm zugewiesenen Zeitpunkt auszuführen, da seine Toleranz standardmäßig Null ist.
Das Hinzufügen einer Timertoleranz ist eine einfache Möglichkeit, den Energieverbrauch zu senken. Auf diese Weise kann das System eine Timer-Aktion zwischen der zugewiesenen Zeit und der zugewiesenen Zeit
plus der Toleranzzeit ausführen - jedoch niemals vor dem zugewiesenen Intervall.
Bei Timern, die nur einmal ausgeführt werden, wird der Toleranzwert ignoriert.
Fügen Sie in der Methode
createTimer () unmittelbar nach der
Timerzuweisung die
folgende Zeile hinzu:
timer?.tolerance = 0.1
Starten Sie die App. In diesem speziellen Fall ist der Effekt nicht offensichtlich (wir haben nur einen Timer). In der realen Situation mehrerer Timer erhalten Ihre Benutzer jedoch eine reaktionsschnellere Benutzeroberfläche und die Anwendung ist energieeffizienter.

Timer im Hintergrund
Interessanterweise, was passiert mit Timern, wenn eine Anwendung in den Hintergrund tritt?
Fügen Sie dazu den Code ganz am Anfang der
updateTimer () -Methode hinzu:
if let fireDateDescription = timer?.fireDate.description { print(fireDateDescription) }
Auf diese Weise können wir Timer-Ereignisse in der Konsole verfolgen.
Führen Sie die Anwendung aus und fügen Sie die Aufgabe hinzu. Drücken Sie nun die Home-Taste auf Ihrem Gerät und kehren Sie zu unserer Anwendung zurück.
In der Konsole sehen Sie ungefähr Folgendes:

Wie Sie sehen können, hält iOS alle laufenden Anwendungszeitgeber an, wenn die Anwendung in den Hintergrund tritt. Wenn die Anwendung aktiv wird, setzt iOS die Timer fort.
Grundlegendes zu Run Loops
Eine Ausführungsschleife ist eine Ereignisschleife, die die Arbeit plant und eingehende Ereignisse verarbeitet. Der Zyklus hält den Thread beschäftigt, während er ausgeführt wird, und versetzt ihn in einen "Ruhezustand", wenn keine Arbeit dafür vorhanden ist.
Jedes Mal, wenn Sie die Anwendung starten, erstellt das System den Hauptthread der Anwendung. Für jeden Thread wird eine automatisch erstellte Ausführungsschleife erstellt.
Aber warum sind Ihnen all diese Informationen jetzt wichtig? Jetzt startet jeder Timer im Hauptthread und tritt der Ausführungsschleife bei. Sie wissen wahrscheinlich, dass der Hauptthread mit dem Rendern der Benutzeroberfläche, dem Verarbeiten von Berührungen usw. beschäftigt ist. Wenn der Haupt-Thread mit etwas beschäftigt ist, reagiert die Benutzeroberfläche Ihrer Anwendung möglicherweise nicht mehr (hängt).
Haben Sie bemerkt, dass der Zeitstempel in der Zelle beim Ziehen der Tabellenansicht nicht aktualisiert wird?

Sie können dieses Problem lösen, indem Sie dem Laufzyklus mitteilen, dass Timer in einem anderen Modus gestartet werden sollen.
Grundlegendes zu Laufzyklusmodi
Der Ausführungszyklusmodus besteht aus einer Reihe von Eingabequellen, z. B. Berühren des Bildschirms oder Klicken mit der Maus, die zur Überwachung eingestellt werden können, und einer Reihe von "Beobachtern", die Benachrichtigungen erhalten.
In iOS gibt es drei Laufzeitmodi:
Standard : Eingabequellen, die keine NSConnectionObjects sind, werden verarbeitet.
häufig : Es wird eine Reihe von Eingabezyklen verarbeitet, für die Sie eine Reihe von Eingabequellen, Timern und "Beobachtern" definieren können.
Tracking : Die Benutzeroberfläche der Anwendung wird verarbeitet.
Für unsere Anwendung ist der am besten geeignete Modus
üblich . Um es zu verwenden, ersetzen Sie den Inhalt der Methode
createTimer () durch Folgendes:
if timer == nil { let timer = Timer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true) RunLoop.current.add(timer, forMode: .common) timer.tolerance = 0.1 self.timer = timer }
Der Hauptunterschied zum vorherigen Code besteht darin, dass wir diesen Timer vor dem Zuweisen des Timers zum TaskListViewController im
allgemeinen Modus zur Ausführungsschleife hinzufügen.
Kompilieren Sie die Anwendung und führen Sie sie aus.

Jetzt werden die Zeitstempel der Zellen aktualisiert, auch wenn die Tabelle gescrollt wird.
Fügen Sie eine Animation hinzu, um alle Aufgaben zu erledigen
Jetzt fügen wir dem Benutzer eine Glückwunschanimation hinzu, um alle Aufgaben zu erledigen - der Ball steigt vom unteren Bildschirmrand nach ganz oben.
Fügen Sie diese Variablen am Anfang des TaskListViewController hinzu:
Der Zweck dieser Variablen ist:
- Animation Timer Speicher.
- Speicherung der Zeit für den Beginn und das Ende der Animation.
- Animationsdauer.
- Animationshöhe.
Fügen Sie nun am Ende der Datei
TaskListViewController.swift die folgende TaskListViewController-
Erweiterung hinzu :
Hier machen wir folgendes:
- Berechnen Sie die Höhe der Animation und ermitteln Sie die Höhe des Gerätebildschirms
- Zentrieren Sie den Ball außerhalb des Bildschirms und stellen Sie seine Sichtbarkeit ein
- Weisen Sie die Start- und Endzeit der Animation zu
- Wir starten den Animations-Timer und aktualisieren die Animation 60 Mal pro Sekunde
Jetzt müssen wir die eigentliche Logik für die Aktualisierung der Glückwunschanimation erstellen. Fügen Sie diesen Code nach
showCongratulationAnimation () hinzu :
func updateAnimation() {
Was machen wir:
- Überprüfen Sie, ob endTime und startTime zugewiesen sind
- Aktuelle Zeit konstant speichern
- Wir stellen sicher, dass die letzte Zeit noch nicht gekommen ist. Wenn es bereits angekommen ist, aktualisieren Sie den Timer und verstecken Sie unseren Ball
- Berechnen Sie die neue y-Koordinate des Balls
- Die horizontale Position des Balls wird relativ zur vorherigen Position berechnet
Ersetzen Sie nun
// TODO: Animation hier in
showCongratulationAnimation () durch diesen Code:
self.updateAnimation()
Jetzt wird
updateAnimation () aufgerufen, wenn ein Timer-Ereignis auftritt.
Hurra, wir haben gerade eine Animation erstellt. Wenn die Anwendung gestartet wird, passiert jedoch nichts Neues ...
Animation anzeigen
Wie Sie wahrscheinlich vermutet haben, gibt es nichts, was unsere neue Animation „starten“ könnte. Dazu benötigen wir eine andere Methode. Fügen Sie diesen Code der Animationserweiterung TaskListViewController hinzu:
func showCongratulationsIfNeeded() { if taskList.filter({ !$0.completed }).count == 0 { showCongratulationAnimation() } }
Wir werden diese Methode immer dann aufrufen, wenn der Benutzer die Aufgabe als erledigt markiert und prüft, ob alle Aufgaben erledigt sind. In diesem
Fall wird
showCongratulationAnimation () aufgerufen .
Fügen Sie dieser Methode abschließend am Ende von
tableView einen Aufruf
hinzu (_: didSelectRowAt :) :
showCongratulationsIfNeeded()
Starten Sie die Anwendung, erstellen Sie einige Aufgaben, markieren Sie sie als erledigt - und Sie werden unsere Animation sehen!

Wir stoppen den Timer
Wenn Sie sich die Konsole ansehen, werden Sie feststellen, dass der Timer weiterhin funktioniert, obwohl der Benutzer alle erledigten Aufgaben markiert hat. Dies ist völlig sinnlos, daher ist es sinnvoll, den Timer anzuhalten, wenn er nicht benötigt wird.
Erstellen Sie zunächst eine neue Methode, um den Timer zu stoppen:
func cancelTimer() { timer?.invalidate() timer = nil }
Dadurch wird der Timer aktualisiert und auf Null zurückgesetzt, damit wir ihn später wieder korrekt erstellen können.
invalidate () ist die einzige Möglichkeit, Timer aus der Ausführungsschleife zu entfernen. Die Ausführungsschleife entfernt die starke Timer-Referenz entweder unmittelbar nach dem Aufruf von
invalidate () oder etwas später.
Ersetzen Sie nun die Methode showCongratulationsIfNeeded () wie folgt:
func showCongratulationsIfNeeded() { if taskList.filter({ !$0.completed }).count == 0 { cancelTimer() showCongratulationAnimation() } else { createTimer() } }
Wenn der Benutzer nun alle Aufgaben erledigt hat, setzt die Anwendung zuerst den Timer zurück und zeigt dann die Animation an. Andernfalls wird versucht, einen neuen Timer zu erstellen, falls dieser noch nicht vorhanden ist.
Starten Sie die App.

Jetzt stoppt der Timer und startet neu, wie es sollte.
CADisplayLink für reibungslose Animation
Der Timer ist keine ideale Wahl für die Steuerung von Animationen. Möglicherweise haben Sie einige Frames bemerkt, die Animationen überspringen, insbesondere wenn Sie die Anwendung im Simulator ausführen.
Wir stellen den Timer auf 60Hz ein. Somit aktualisiert der Timer die Animation alle 16 ms. Betrachten Sie die Situation genauer:

Bei Verwendung von
Timer wissen wir nicht genau, wann die Aktion gestartet wurde. Dies kann entweder am Anfang oder am Ende des Frames geschehen. Angenommen, der Timer läuft in der Mitte jedes Frames (blaue Punkte im Bild). Das einzige, was wir sicher wissen, ist, dass der Anruf alle 16 ms erfolgt.
Jetzt haben wir nur noch 8 ms Zeit, um die Animation auszuführen, und dies reicht möglicherweise nicht für unsere Animation aus. Schauen wir uns den zweiten Frame in der Abbildung an. Das zweite Bild kann nicht in der vorgegebenen Zeit fertiggestellt werden, daher setzt die Anwendung das zweite Bild der Animation zurück.
CADisplayLink wird uns helfen
CADisplayLink wird einmal pro Frame aufgerufen und versucht, echte Animationsframes so weit wie möglich zu synchronisieren. Jetzt stehen Ihnen alle 16 ms zur Verfügung und iOS lässt keinen einzigen Frame fallen.
Um
CADisplayLink verwenden zu
können , müssen Sie
animationTimer durch einen neuen Typ ersetzen.
Ersetzen Sie diesen Code
var animationTimer: Timer?
zu diesem:
var displayLink: CADisplayLink?
Sie haben
Timer durch
CADisplayLink ersetzt .
CADisplayLink ist eine Timer-Ansicht, die an den vertikalen Scan der Anzeige gebunden ist. Dies bedeutet, dass die GPU des Geräts angehalten wird, bis der Bildschirm weiterhin GPU-Befehle verarbeiten kann. Auf diese Weise erhalten wir eine reibungslose Animation.
Ersetzen Sie diesen Code
var startTime: TimeInterval?, endTime: TimeInterval?
zu diesem:
var startTime: CFTimeInterval?, endTime: CFTimeInterval?
Sie haben
TimeInterval durch
CFTimeInterval ersetzt , was für die Arbeit mit CADisplayLink erforderlich ist.
Ersetzen Sie den Text der
showCongratulationAnimation () -Methode durch
Folgendes :
func showCongratulationAnimation() {
Was machen wir hier:
- Stellen Sie die Höhe der Animation, die Koordinaten des Balls und die Sichtbarkeit ein - ungefähr so wie zuvor.
- Initialisieren Sie startTime mit CACurrentMediaTime () (anstelle von Date ()).
- Wir erstellen eine Instanz der CADisplayLink- Klasse und fügen sie im allgemeinen Modus der Ausführungsschleife hinzu.
Ersetzen
Sie nun
updateAnimation () durch den folgenden Code:
- Fügen Sie der Methodensignatur objc hinzu (für CADisplayLink erfordert der Selektorparameter eine solche Signatur).
- Ersetzen Sie die Initialisierung durch Date () , um das Datum von CoreAnimation zu initialisieren.
- Ersetzen Sie den Aufruf von animationTimer.invalidate () durch eine Pause von CADisplayLink und machen Sie ihn ungültig. Dadurch wird auch CADisplayLink aus der Ausführungsschleife entfernt.
Starten Sie die App!

Großartig! Wir haben die
Timer- basierte Animation erfolgreich durch einen geeigneteren
CADisplayLink ersetzt - und die Animation flüssiger gemacht, ohne zu ruckeln.
Fazit
In diesem Handbuch haben Sie herausgefunden, wie die
Timer- Klasse in iOS funktioniert, wie der Ausführungszyklus
abläuft und wie Ihre Anwendung in Bezug auf die Benutzeroberfläche
reaktionsfähiger wird und wie Sie
CADisplayLink anstelle von Timer für eine reibungslose Animation verwenden.