Каков наилучший способ запуска функции/блока с точной (в пределах 1 секунды) датой/временем в Swift на iOS?
Я пытался использовать Timer
init
с fireAt
, но обнаружил, что точность часто снижается на несколько секунд. Установка точности таймера на значение, подобное 0,5, не дала лучших результатов.
Я также пытался использовать GCD с DispatchQueue.main.asyncAfter(.now() + someDelay)
, и в моем тестировании он также часто отличался на несколько секунд.
Я остановился на создании таймера, который срабатывает раз в секунду, а затем я проверяю, истекла ли текущая дата/время желаемого значения, а затем вручную запускаю свой код. Кажется, что может быть лучший способ сделать это, но у меня нет идей. Спасибо!
Если вы создадите таймер GCD с makeTimerSource
с флагом .strict
, он откажется от объединения таймеров:
The system makes its best effort to observe the timer’s specified
leeway
value, even if the value is smaller than the default leeway.
Например:
var timer: DispatchSourceTimer?
func startTimer(in interval: DispatchTimeInterval, block: @escaping () -> Void) {
timer = DispatchSource.makeTimerSource(flags: .strict, queue: .main)
timer?.setEventHandler(handler: block)
timer?.schedule(deadline: .now() + interval, leeway: .milliseconds(500))
timer?.activate()
}
А потом:
startTimer(in: .seconds(240)) { [weak self] in
self?.foo()
}
Обратите внимание, что в отличие от Timer
, который сохраняется за RunLoop
, на который вы его запланировали, вы должны сохранить собственную сильную ссылку на таймер GCD, как показано выше. Вы можете создать Timer
свойства weak
(чтобы они освобождались, когда RunLoop
освобождает их), но вам нужны сильные ссылки на таймеры GCD.
Несколько предостережений:
Обратите внимание на использование списка захвата [weak self]
. Как правило, вы хотите избежать сильного эталонного цикла (особенно если таймер был повторяющимся таймером).
Этот шаблон следует использовать только в случае крайней необходимости, поскольку вы отказываетесь от функций энергосбережения, предлагаемых объединением таймеров. Я ожидаю, что это более энергоэффективно, чем подход с повторяющимся таймером, но менее энергоэффективно, чем позволить ОС использовать свое усмотрение для объединения своих таймеров.
Этот шаблон предполагает, что очередь, для которой он запланирован, не заблокирована. Очевидно, что мы никогда не блокируем основной поток, но вы всегда можете рассмотреть возможность использования собственной целевой очереди.
Ради будущих читателей, этот шаблон, очевидно, применяется, когда приложение действительно работает. Если приложение приостановлено, таймеры не срабатывают. Если вы хотите, чтобы пользователь был уведомлен в назначенное время, даже если приложение в данный момент не запущено, вы должны использовать локально запланированный уведомление пользователя.
Спасибо за подробный ответ @Rob. Это именно то, что я искал.