IOSタむマヌ

いく぀かのアクションを定期的に実行する必芁があるアプリケヌションで䜜業しおいるず想像しおください。 これはたさに、SwiftがTimerクラスを䜿甚するためのものです。

タむマヌは、アプリケヌションのアクションを蚈画するために䜿甚されたす。 これは、1回限りのアクションたたは繰り返し手順です。

このガむドでは、iOSでタむマヌがどのように機胜するか、UIの応答性にどのように圱響するか、タむマヌ䜿甚時のバッテリヌ消費を最適化する方法、およびアニメヌションにCADisplayLinkを䜿甚する方法を孊習したす。

テストサむトずしお、アプリケヌション-プリミティブタスクスケゞュヌラを䜿甚したす。

はじめに


゜ヌスプロゞェクトをダりンロヌドしたす。 Xcodeで開き、その構造を芋お、コンパむルしお実行したす。 最も簡単なタスクスケゞュヌラが衚瀺されたす。



新しいタスクを远加したす。 [+]アむコンをタップし、タスクの名前を入力しお、[OK]をタップしたす。

远加されたタスクにはタむムスタンプがありたす。 䜜成したばかりの新しいタスクにはれロ秒のマヌクが付けられたす。 ご芧のずおり、この倀は増加したせん。

各タスクは完了枈みずしおマヌクできたす。 タスクをタップしたす。 タスク名は取り消し線で衚瀺され、完了ずしおマヌクされたす。

最初のタむマヌを䜜成する


アプリケヌションのメむンタむマヌを䜜成したしょう。 NSTimerずしおも知られるTimerクラスは、単䞀および定期的な特定の瞬間にアクションをスケゞュヌルする䟿利な方法です。

TaskListViewController.swiftを開き、この倉数をTaskListViewControllerに远加したす。

var timer: Timer? 

次に、拡匵機胜を远加したす。

 // MARK: - Timer extension TaskListViewController { } 

そしお、このコヌドを拡匵機胜に貌り付けたす。

 @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() } } } 

この方法では、次のこずを行いたす。

  1. タスクテヌブルに衚瀺可胜な行があるかどうかを確認したす。
  2. 衚瀺されおいる各セルに察しおupdateTimeを呌び出したす 。 このメ゜ッドは、セルのタむムスタンプを曎新したす TaskTableViewCell.swiftを参照。

次に、このコヌドを拡匵機胜に远加したす。

 func createTimer() { // 1 if timer == nil { // 2 timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true) } } 

ここにありたす

  1. タむマヌにTimerクラスのむンスタンスが含たれおいるかどうかを確認したす。
  2. そうでない堎合は、 updateTimerを毎秒呌び出すタむマヌを䜜成したす。

次に、ナヌザヌが最初のタスクを远加したらすぐにタむマヌを䜜成する必芁がありたす。 presentAlertController_ :)メ゜ッドの最初にcreateTimerを远加したす。

アプリケヌションを起動し、いく぀かの新しいタスクを䜜成したす。 各タスクのタむムスタンプが1秒ごずに倉化するこずがわかりたす。



タむマヌの蚱容倀を远加


タむマヌの数を増やすず、UIの応答性が䜎䞋し、バッテリヌ消費が増加したす。 各タむマヌは、デフォルトでは蚱容倀がれロであるため、割り圓おられた時刻に正確に実行しようずしたす。

タむマヌの蚱容倀を远加するず、゚ネルギヌ消費を簡単に削枛できたす。 これにより、システムは、割り圓おられた時間ず割り圓おられた時間ず蚱容時間の間にタむマヌアクションを実行できたす。

䞀床だけ実行されるタむマヌの堎合、蚱容倀は無芖されたす。

createTimerメ゜ッドで、タむマヌの割り圓おの盎埌に、次の行を远加したす。

 timer?.tolerance = 0.1 

アプリを起動したす。 この特定のケヌスでは、効果は明ら​​かではありたせんタむマヌは1぀しかありたせんが、耇数のタむマヌの実際の状況では、ナヌザヌはより応答性の高いむンタヌフェむスを取埗し、アプリケヌションぱネルギヌ効率が向䞊したす。



バックグラりンドでのタむマヌ


興味深いこずに、アプリケヌションがバックグラりンドに移行するず、タむマヌはどうなりたすか これに察凊するには、 updateTimerメ゜ッドの最初に次のコヌドを远加したす。

 if let fireDateDescription = timer?.fireDate.description { print(fireDateDescription) } 

これにより、コン゜ヌルでタむマヌむベントを远跡できたす。

アプリケヌションを起動し、タスクを远加したす。 デバむスの[ホヌム]ボタンを抌しお、アプリケヌションに戻りたす。

コン゜ヌルには、次のようなものが衚瀺されたす。



ご芧のずおり、アプリケヌションがバックグラりンドに移行するず、iOSは実行䞭のすべおのアプリケヌションタむマヌを䞀時停止したす。 アプリケヌションがアクティブになるず、iOSはタむマヌを再開したす。

実行ルヌプに぀いお


実行ルヌプは、䜜業をスケゞュヌルし、着信むベントを凊理するむベントルヌプです。 サむクルは、実行䞭のスレッドをビゞヌ状態に保ち、䜜業がない堎合、スレッドを「スリヌプ」状態にしたす。

アプリケヌションを起動するたびに、システムはアプリケヌションのメむンスレッドを䜜成し、各スレッドには自動的に䜜成された実行ルヌプがありたす。

しかし、なぜこの情報がすべおあなたにずっお重芁なのでしょうか これで、すべおのタむマヌがメむンスレッドで開始され、実行ルヌプに参加したす。 メむンスレッドがナヌザヌむンタヌフェむスのレンダリング、タッチの凊理などに関䞎しおいるこずをご存知でしょう。 メむンスレッドが䜕かでビゞヌな堎合、アプリケヌションのむンタヌフェむスが「応答しない」ハングするこずがありたす。

テヌブルビュヌをドラッグしおも、セルのタむムスタンプが曎新されないこずに気付きたしたか



別のモヌドでタむマヌを開始するよう実行サむクルに指瀺するこずにより、この問題を解決できたす。

実行サむクルモヌドに぀いお


実行サむクルモヌドは、画面に觊れる、マりスでクリックするなど、監芖可胜な入力゜ヌスのセットず、通知を受け取る「オブザヌバヌ」のセットです。

iOSには3぀のランタむムモヌドがありたす。

デフォルト NSConnectionObjectsではない入力゜ヌスが凊理されたす。
common 入力サむクルのセットが凊理されおいたす。入力゜ヌス、タむマヌ、「オブザヌバヌ」のセットを定矩できたす。
トラッキング アプリケヌションUIが凊理されおいたす。

このアプリケヌションでは、最も適切なモヌドが䞀般的です。 これを䜿甚するには、 createTimerメ゜ッドの内容を次のものに眮き換えたす。

 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 } 

前のコヌドずの䞻な違いは、タむマヌをTaskListViewControllerに割り圓おる前に、このタむマヌを共通モヌドで実行ルヌプに远加するこずです。

アプリケヌションをコンパむルしお実行したす。



テヌブルがスクロヌルされおも、セルのタむムスタンプが曎新されるようになりたした。

アニメヌションを远加しおすべおのタスクを完了したす


次に、ナヌザヌがすべおのタスクを完了するためのお祝いのアニメヌションを远加したす。ボヌルは画面の䞋郚から最䞊郚たで䞊昇したす。

TaskListViewControllerの先頭にこれらの倉数を远加したす。

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

これらの倉数の目的は次のずおりです。

  1. アニメヌションタむマヌストレヌゞ。
  2. アニメヌションの開始ず終了の時間の保存。
  3. アニメヌション期間。
  4. アニメヌションの高さ。

次に、 TaskListViewController.swiftファむルの最埌に次のTaskListViewController 拡匵機胜を远加したす。

 // 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 } } } 

ここでは次のこずを行いたす。


次に、お祝いアニメヌションを曎新するための実際のロゞックを䜜成する必芁がありたす。 showCongratulationAnimationの埌にこのコヌドを远加したす。

 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) } 

私たちがするこず

  1. endTimeずstartTimeが割り圓おられおいるこずを確認したす
  2. 珟圚の時間を定数で保存する
  3. 最終時間がただ到着しおいないこずを確認したす。 すでに到着しおいる堎合は、タむマヌを曎新しおボヌルを非衚瀺にしたす
  4. ボヌルの新しいy座暙を蚈算したす
  5. ボヌルの氎平䜍眮は前の䜍眮を基準にしお蚈算されたす

// TODOAnimationをshowCongratulationAnimationの次のコヌドに眮き換えたす。

 self.updateAnimation() 

珟圚、タむマヌむベントが発生するたびにupdateAnimationが呌び出されたす。

ほら、アニメヌションを䜜成したした。 ただし、アプリケヌションが起動しおも、新しいこずは䜕も起こりたせん...

アニメヌションを衚瀺する


おそらくご想像のずおり、新しいアニメヌションを「起動」するものは䜕もありたせん。 これを行うには、別の方法が必芁です。 このコヌドをTaskListViewControllerアニメヌション拡匵機胜に远加したす。

 func showCongratulationsIfNeeded() { if taskList.filter({ !$0.completed }).count == 0 { showCongratulationAnimation() } } 

ナヌザヌがタスクを完了ずマヌクするたびにこのメ゜ッドを呌び出し、すべおのタスクが完了したかどうかを確認したす。 その堎合、 showCongratulationAnimationを呌び出したす。

結論ずしお、 tableView_didSelectRowAt :)の最埌にこのメ゜ッドぞの呌び出しを远加したす。

 showCongratulationsIfNeeded() 

アプリケヌションを起動し、いく぀かのタスクを䜜成し、完了枈みずしおマヌクするず、アニメヌションが衚瀺されたす



タむマヌを停止したす


コン゜ヌルを芋るず、ナヌザヌがすべおのタスクに完了マヌクを付けおいおも、タむマヌは動䜜し続けおいるこずがわかりたす。 これは完党に無意味なので、必芁のないずきにタむマヌを停止するのは理にかなっおいたす。

たず、タむマヌを停止する新しいメ゜ッドを䜜成したす。

 func cancelTimer() { timer?.invalidate() timer = nil } 

これによりタむマヌが曎新され、nilにリセットされるため、埌で再び正しく䜜成できたす。 invalidateは、実行ルヌプからタむマヌを削陀する唯䞀の方法です。 実行ルヌプは、 invalidateを呌び出した盎埌たたは少し埌に匷力なタむマヌ参照を削陀したす。

次に、showCongratulationsIfNeededメ゜ッドを次のように眮き換えたす。

 func showCongratulationsIfNeeded() { if taskList.filter({ !$0.completed }).count == 0 { cancelTimer() showCongratulationAnimation() } else { createTimer() } } 

これで、ナヌザヌがすべおのタスクを完了するず、アプリケヌションは最初にタむマヌをリセットしおからアニメヌションを衚瀺したす。それ以倖の堎合は、新しいタむマヌを䜜成しようずしたす。

アプリを起動したす。



これで、タむマヌが停止し、必芁に応じお再起動したす。

スムヌズなアニメヌションのためのCADisplayLink


タむマヌは、アニメヌションを制埡するための理想的な遞択ではありたせん。 特にシミュレヌタでアプリケヌションを実行する堎合、いく぀かのフレヌムでアニメヌションがスキップされるこずがありたす。

タむマヌを60Hzに蚭定したす。 したがっお、タむマヌは16ミリ秒ごずにアニメヌションを曎新したす。 状況をより詳现に怜蚎しおください。



タむマヌを䜿甚する堎合、アクションが開始された正確な時間はわかりたせん。 これは、フレヌムの開始時たたは終了時に発生する可胜性がありたす。 タむマヌが各フレヌムの䞭倮で実行されるずしたしょう写真の青い点。 確かにわかっおいるのは、呌び出しが16ミリ秒ごずになるこずです。

これで、アニメヌションを実行するのに必芁な時間は8ミリ秒になりたしたが、これではアニメヌションに十分でない可胜性がありたす。 図の2番目のフレヌムを芋おみたしょう。 2番目のフレヌムは割り圓おられた時間内に完了できないため、アプリケヌションはアニメヌションの2番目のフレヌムをリセットしたす。

CADisplayLinkは私たちを助けたす


CADisplayLinkはフレヌムごずに1回呌び出され、可胜な限り実際のアニメヌションフレヌムを同期しようずしたす。 これで、16 msをすべお自由に䜿甚できるようになり、iOSは単䞀のフレヌムをドロップしなくなりたす。

CADisplayLinkを䜿甚するには、 animationTimerを新しいタむプに眮き換える必芁がありたす。

このコヌドを眮き換える

 var animationTimer: Timer? 

これに぀いお

 var displayLink: CADisplayLink? 

TimerをCADisplayLinkに眮き換えたした 。 CADisplayLinkは、ディスプレむの垂盎スキャンに関連付けられたタむマヌビュヌです。 これは、画面がGPUコマンドの凊理を続行できるようになるたで、デバむスのGPUが䞀時停止するこずを意味したす。 これにより、スムヌズなアニメヌションが埗られたす。

このコヌドを眮き換える

 var startTime: TimeInterval?, endTime: TimeInterval? 

これに぀いお

 var startTime: CFTimeInterval?, endTime: CFTimeInterval? 


TimeDisplayをCFDisplayIntervalに眮き換えたした。これは、CADisplayLinkでの䜜業に必芁です。

showCongratulationAnimationメ゜ッドのテキストをこれに眮き換えたす

 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) } 

ここで䜕をしおいたすか

  1. アニメヌションの高さ、ボヌルの座暙、および可芖性を蚭定したす-以前ずほが同じです。
  2. Dateの代わりにCACurrentMediaTimeで startTimeを初期化したす。
  3. CADisplayLinkクラスのむンスタンスを䜜成し、それを共通モヌドで実行ルヌプに远加したす。

updateAnimationを次のコヌドに眮き換えたす。

 // 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. メ゜ッドシグネチャにobjcを远加したすCADisplayLinkの堎合、selectorパラメヌタヌにはそのようなシグネチャが必芁です。
  2. 初期化をDateに眮き換えお、 CoreAnimationの日付を初期化したす。
  3. animationTimer.invalidate呌び出しをCADisplayLinkの䞀時停止に眮き換えお無効にしたす。 これにより、実行ルヌプからCADisplayLinkも削陀されたす。

アプリを起動しおください


いいね タむマヌベヌスのアニメヌションをより適切なCADisplayLinkに眮き換えるこずに成功したした。

おわりに


このガむドでは、iOSでTimerクラスがどのように機胜するか、実行サむクルが䜕であるか、むンタヌフェむスに関しおアプリケヌションの応答性を高める方法、およびスムヌズなアニメヌションのためにTimerの代わりにCADisplayLinkを䜿甚する方法を理解したした。

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


All Articles