Начните с развязки ваших ожиданий.
«Часы» - это контейнер для периода времени, с которого они были начаты до настоящего времени.Кроме того, он может быть «перезапущен», поэтому может потребоваться узнать, как долго длился каждый предыдущий цикл выполнения, а затем он будет добавлен к общей продолжительности «часов»
. Timer
- это простоспособ запустить некоторый код на периодических основах.Поскольку Timer
гарантирует только «как минимум» период, его следует избегать для простого добавления счетчика, поскольку это может вызвать смещение в ваших вычислениях (для простых часов это, вероятно, не имеет большого значения, но если вам нужен какой-либо видточности, лучше избегать этого)
SimpleClock
import Foundation
public class SimpleClock {
internal var startedAt: Date? = nil
internal var totalRunningTime: TimeInterval = 0 // Used for pause/resume
var isRunning: Bool = false {
didSet {
if isRunning {
startedAt = Date()
} else {
totalRunningTime += currentCycleDuration
self.startedAt = nil
}
}
}
// This is the amount of time that this cycle has been running,
// that is, the amount of time since the clock was started to now.
// It does not include other cycles
internal var currentCycleDuration: TimeInterval {
guard let startedAt = startedAt else {
return 0
}
return Date().timeIntervalSince(startedAt)
}
func reset() {
isRunning = false
totalRunningTime = 0
}
// This is the "total" amount of time the clock has been allowed
// to run for, excluding periods when the clock was paused
var duration: TimeInterval {
return totalRunningTime + currentCycleDuration
}
}
Хорошо, это довольно базовая концепция.Это просто контейнер для записи, когда «цикл» запускается и останавливается, и для управления «общей» продолжительностью (циклы пуска / паузы / возобновления)
Это все хорошо и хорошо, но нам действительно нужен какой-то способопределить, имеет ли период «тайм-аут» или нет.
AlarmClock
import Foundation
class AlarmClock: SimpleClock {
var timeout: TimeInterval = 0
var hasExpired: Bool {
return duration >= timeout
}
var timeRemaining: TimeInterval {
return max(timeout - duration, 0)
}
}
Все, что он делает, это добавляет концепцию периода «тайм-аут» и предоставляет некоторые дополнительные функции, которые позволяют использоватьлегко определить, истекли ли часы и сколько времени осталось
Пример
Хорошо, это все хорошо, но как это работает (и помогает нам)
Хорошо, это действительно простой пример.Он имеет метку и две кнопки.Одна кнопка запускает / приостанавливает часы, а другая сбрасывает их.
Метка отображает как время работы, так и оставшееся время будильника.Если часы истекают, они автоматически сбрасываются.
Класс содержит Timer
, который периодически «тикает» и позволяет коду проверять текущее состояние будильника.
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var durationLabel: UILabel!
@IBOutlet weak var cycleButton: UIButton!
@IBOutlet weak var resetButton: UIButton!
let alarmClock: AlarmClock = {
let clock = AlarmClock()
clock.timeout = 10.0
return clock
}()
var timer: Timer? = nil
var durationFormatter: DateComponentsFormatter {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.minute, .second]
formatter.unitsStyle = .abbreviated
return formatter
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func cycleClock(_ sender: Any) {
alarmClock.isRunning = !alarmClock.isRunning
if alarmClock.isRunning {
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(tick), userInfo: nil, repeats: true)
} else {
timer?.invalidate()
timer = nil
}
updateDurationLabel()
updateCycleButtonState()
}
@IBAction func restartClock(_ sender: Any) {
timer?.invalidate()
timer = nil
alarmClock.reset()
updateDurationLabel()
updateCycleButtonState()
}
func updateCycleButtonState() {
if alarmClock.isRunning {
cycleButton.setTitle("Pause", for: [])
} else {
cycleButton.setTitle("Start", for: [])
}
}
func updateDurationLabel() {
durationLabel.text = "\(durationFormatter.string(from: alarmClock.duration)!)/\(durationFormatter.string(from: alarmClock.timeRemaining)!)"
}
@objc func tick() {
print("click")
updateDurationLabel()
if alarmClock.hasExpired {
restartClock(self)
}
}
}
Теперь вы также можете добавить своего рода «внутренний» поток, чтобы периодически проверять состояние часов и вызывать делегат, который затем может быть использован для обновленияПользовательский интерфейс, но намерение здесь состоит в том, чтобы разделить проблемы, и это означает, что вы не добавляете еще один поток в систему без необходимости (не говоря уже о том, что вы не можете этого сделать, но это просто еще один уровень сложности, который я не хотелдобавить;))