eighthours -= 1
в обработчике таймера немного проблематичен, поскольку предполагается, что Timer
будет срабатывать без перерыва в нужном timeInterval
. Но вы должны учитывать прерывания в Timer
(например, пользовательский интерфейс по какой-то причине на мгновение блокируется, пользователь полностью покидает приложение и возвращается и т. Д.).
Мы часто переключаемся с «уменьшить счетчик при каждом вызове обработчика таймера» на «выяснить, в какое время мы хотим, чтобы таймер истек». Это отделяет «модель» (время остановки) от «вида» (частота, с которой обновляется пользовательский интерфейс). Таким образом, если вы позже решите обновить свой пользовательский интерфейс с большей частотой (например, показывать миллисекунды, а не секунды, вероятно, используя CADisplayLink
вместо Timer
), это не изменит модель управления приложением. И это делает ваше приложение неуязвимым для кратковременных прерываний, которые могут повлиять на таймер.
Если вы примете этот шаблон, то вы можете передать это «время остановки», свою модель, от контроллера представления к контроллеру представления, и каждый контроллер представления может запускать и останавливать свой собственный таймер в соответствии с требованиями требуемого UX для этой сцены. ,
Итак, чтобы запустить таймер, который остановится через 8 секунд:
var stopTime: Date? // set this when you start the timer
func startTimer() {
stopTime = Date().addingTimeInterval(8)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(jobtime(_:)), userInfo: nil, repeats: true)
}
И обработчик таймера может определить, сколько времени осталось с помощью timeIntervalSince
для вычисления разницы с плавающей запятой в секундах между двумя датами.
@objc func jobtime(_ timer: Timer) {
let now = Date()
guard let stopTime = stopTime, now < stopTime else {
timer.invalidate()
return
}
let timeRemaining = stopTime.timeIntervalSince(now)
...
}
Я также обновил jobtime
параметром timer
, чтобы вы могли сразу увидеть, что это обработчик Timer
.
К вашему сведению, ваш код содержит строгую ссылку на контроллер представления, которая будет препятствовать его выпуску. Этот селектор на основе Timer
сохраняет сильную ссылку на свою цель, а цикл выполнения сохраняет ссылку на Timer
, поэтому ваш контроллер представления не будет освобожден, пока вы не invalidate
Timer
.
Существует несколько вариантов решения этой проблемы:
Запуск и остановка таймеров при появлении и исчезновении представлений:
weak var timer: Timer?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(jobtime(_:)), userInfo: nil, repeats: true)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
timer?.invalidate()
}
@objc func jobtime(_ timer: Timer) { ... }
Обратите внимание, что мы делаем это в viewDidAppear
и viewDidDisappear
(а не viewDidLoad
), чтобы гарантировать, что запуск и остановка таймеров всегда сбалансированы.
Другой шаблон - использовать основанный на блоках Timer
, использовать [weak self]
ссылку, чтобы избежать таймера, сохраняющего сильную ссылку на контроллер представления, а затем вы можете invalidate
это в методе deinit
:
weak var timer: Timer?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
self?.jobtime(timer)
}
}
deinit {
timer?.invalidate()
}
Наконец, если вы хотите обновить пользовательский интерфейс с максимально возможной частотой (например, для отображения миллисекунд), вам, вероятно, следует использовать CADisplayLink
, который является специальным таймером, идеально рассчитанным для обновления пользовательского интерфейса:
private weak var displayLink: CADisplayLink?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let displayLink = CADisplayLink(target: self, selector: #selector(jobtime(_:)))
displayLink.add(to: .main, forMode: .common)
self.displayLink = displayLink
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
displayLink?.invalidate()
}
@objc func jobtime(_ displayLink: CADisplayLink) { ... }
Но общая черта во всех этих подходах состоит в том, что (а) мы исключаем сохранение сильных ссылок, которые будут мешать контроллеру представления освобождаться, когда это необходимо; и (b) мы позволяем каждому контроллеру представления обновлять свой пользовательский интерфейс с любой частотой, которую он хочет.