IOS Timer

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:

 // MARK: - Timer extension TaskListViewController { } 

Und fügen Sie diesen Code in die Erweiterung ein:

 @objc func updateTimer() { // 1 guard let visibleRowsIndexPaths = tableView.indexPathsForVisibleRows else { return } for indexPath in visibleRowsIndexPaths { // 2 if let cell = tableView.cellForRow(at: indexPath) as? TaskTableViewCell { cell.updateTime() } } } 

Bei dieser Methode haben wir:

  1. Überprüfen Sie, ob die Aufgabentabelle sichtbare Zeilen enthält.
  2. 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() { // 1 if timer == nil { // 2 timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true) } } 

Hier sind wir:

  1. Überprüfen Sie, ob der Timer eine Instanz der Timer- Klasse enthält.
  2. 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:

 // 1 var animationTimer: Timer? // 2 var startTime: TimeInterval?, endTime: TimeInterval? // 3 let animationDuration = 3.0 // 4 var height: CGFloat = 0 

Der Zweck dieser Variablen ist:

  1. Animation Timer Speicher.
  2. Speicherung der Zeit für den Beginn und das Ende der Animation.
  3. Animationsdauer.
  4. Animationshöhe.

Fügen Sie nun am Ende der Datei TaskListViewController.swift die folgende TaskListViewController- Erweiterung hinzu :

 // MARK: - Animation extension TaskListViewController { func showCongratulationAnimation() { // 1 height = UIScreen.main.bounds.height + balloon.frame.size.height // 2 balloon.center = CGPoint(x: UIScreen.main.bounds.width / 2, y: height + balloon.frame.size.height / 2) balloon.isHidden = false // 3 startTime = Date().timeIntervalSince1970 endTime = animationDuration + startTime! // 4 animationTimer = Timer.scheduledTimer(withTimeInterval: 1 / 60, repeats: true) { timer in // TODO: Animation here } } } 

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() { // 1 guard let endTime = endTime, let startTime = startTime else { return } // 2 let now = Date().timeIntervalSince1970 // 3 if now >= endTime { animationTimer?.invalidate() balloon.isHidden = true } // 4 let percentage = (now - startTime) * 100 / animationDuration let y = height - ((height + balloon.frame.height / 2) / 100 * CGFloat(percentage)) // 5 balloon.center = CGPoint(x: balloon.center.x + CGFloat.random(in: -0.5...0.5), y: y) } 

Was machen wir:

  1. Überprüfen Sie, ob endTime und startTime zugewiesen sind
  2. Aktuelle Zeit konstant speichern
  3. 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
  4. Berechnen Sie die neue y-Koordinate des Balls
  5. 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() { // 1 height = UIScreen.main.bounds.height + balloon.frame.size.height balloon.center = CGPoint(x: UIScreen.main.bounds.width / 2, y: height + balloon.frame.size.height / 2) balloon.isHidden = false // 2 startTime = CACurrentMediaTime() endTime = animationDuration + startTime! // 3 displayLink = CADisplayLink(target: self, selector: #selector(updateAnimation)) displayLink?.add(to: RunLoop.main, forMode: .common) } 

Was machen wir hier:

  1. Stellen Sie die Höhe der Animation, die Koordinaten des Balls und die Sichtbarkeit ein - ungefähr so ​​wie zuvor.
  2. Initialisieren Sie startTime mit CACurrentMediaTime () (anstelle von Date ()).
  3. 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:

 // 1 @objc func updateAnimation() { guard let endTime = endTime, let startTime = startTime else { return } // 2 let now = CACurrentMediaTime() if now >= endTime { // 3 displayLink?.isPaused = true displayLink?.invalidate() balloon.isHidden = true } let percentage = (now - startTime) * 100 / animationDuration let y = height - ((height + balloon.frame.height / 2) / 100 * CGFloat(percentage)) balloon.center = CGPoint(x: balloon.center.x + CGFloat.random(in: -0.5...0.5), y: y) } 

  1. Fügen Sie der Methodensignatur objc hinzu (für CADisplayLink erfordert der Selektorparameter eine solche Signatur).
  2. Ersetzen Sie die Initialisierung durch Date () , um das Datum von CoreAnimation zu initialisieren.
  3. 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.

Source: https://habr.com/ru/post/de450172/


All Articles