Таймеры очень удобны в Swift. Они позволяют создавать повторяющиеся задачи, а также планировать выполнение кода с задержкой. В этом руководстве мы рассмотрим, как можно создавать и использовать таймеры в Swift.
Как создать повторяющийся таймер?
Создать повторяющийся таймер просто:
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fire), userInfo: nil, repeats: true) @objc func fire() { print("Таймер!") }
В приведенном выше коде происходит несколько вещей:
- Таймер создается с помощью метода класса Timer.scheduledTimer(…). Возвращаемое значение присваивается константе timer.
- Параметрами scheduledTimer() являются интервал таймера в 1 секунду, который использует механизм target-action. userInfo имеет значение nil, а для параметра repeats установлено значение true.
- Функция fire() вызывается при срабатывании таймера, то есть примерно каждую секунду.
Приведенный выше код должен выполняться в контексте класса, например, в классе контроллера представления. Функция fire() является частью класса, и self относится к экземпляру данного класса.
Также мы можем создать таймер с помощью замыкания:
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in print("Таймер!") })
В приведенном выше коде последний параметр block является замыканием. Замыкание имеет один параметр – timer.
Благодаря синтаксису выходящего замыкания мы можем сделать код таймера еще более кратким:
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in print("Таймер!") }
Управление таймерами
В предыдущем примере мы использовали 5 параметров для создания таймера:
- timeInterval – интервал между срабатываниями таймера в секундах. Имеет тип Double.
- target – экземпляр класса, для которого должна вызываться функция selector. Чаще всего используется self.
- selector – функция, вызываемая при срабатывании таймера.
- userInfo – словарь с данными, которые предоставляются в selector.
- repeats – должен ли повторяться таймер или нет.
Если для параметра repeats установить значение false, таймер сработает только один раз и сразу же отключится.
Мы можем использовать свойство timer для управления остановки таймера.
timer.invalidate()
Вы также можете добавить дополнительную информацию к таймеру с помощью userInfo. Это значение отправляется в функцию запуска таймера через параметр timer:
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fire(timer:)), userInfo: ["score": 10], repeats: true) @objc func fire(timer: Timer) { if let userInfo = timer.userInfo as? [String: Int], let score = userInfo["score"] { print("You scored \(score) points!") } }
В приведенном выше коде мы добавили словарь [“score”: 10] к таймеру. Когда таймер срабатывает, словарь передается функции fire(timer:) как timer.userInfo. Внутри функции fire(timer:) мы проверяем, имеет ли свойство userInfo ожидаемый тип и получаем нужное значение.
Создание таймера обратного отсчета
Представьте, что вы создаете игру. У пользователя есть 60 секунд, чтобы решить головоломку и набрать очки. С помощью таймера обратного отсчета вы можете отслеживать, сколько секунд осталось.
Сначала мы создадим два свойства в верхней части игрового класса:
var timer: Timer? var timeLeft = 60
В какой-то момент игра запускается, поэтому мы также запускаем таймер:
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(onTimerFires), userInfo: nil, repeats: true)
Таймер выполняет каждую секунду функцию onTimerFires():
@objc func onTimerFires() { timeLeft -= 1 timeLabel.text = "\(timeLeft)" if timeLeft <= 0 { timer?.invalidate() timer = nil } }
Каждый раз, когда срабатывает таймер, он вычитает 1 из timeLeft и обновляет оставшееся время. Когда таймер достигает нуля, он становится недействительным.
Таймеры и циклы выполнения
Таймеры работают вместе с циклами выполнения, которые являются частью потоков и параллелизма.
Цикл выполнения проверяет, должен ли срабатывать таймер. Таймер не является механизмом реального времени, потому что срабатывание таймера может совпадать с циклом выполнения, уже выполняющим другую задачу. В результате таймер может сработать позже запланированного.
Вы можете использовать свойство таймера tolerance. Это сообщит системе планирования: «Послушайте, я хочу, чтобы таймер запускался каждую секунду, но меня не волнует, опаздывает ли он на 0,2 секунды». Apple рекомендует установить значением tolerance минимум на 10% времени интервала для повторяющегося таймера.
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fire), userInfo: nil, repeats: true) timer.tolerance = 0.2
При использовании метода класса Timer.scheduledTimer(…) таймер автоматически назначается для текущего цикла выполнения в режиме по умолчанию . Обычно это цикл выполнения основного потока. В результате таймеры могут не срабатывать, когда цикл выполнения основного потока занят, например, когда пользователь вашего приложения взаимодействует с пользовательским интерфейсом.
Вы можете решить эту проблему, вручную запланировав таймер для цикла выполнения:
let timer = Timer(timeInterval: 1.0, target: self, selector: #selector(fire), userInfo: nil, repeats: true) RunLoop.current.add(timer, forMode: .commonModes)
Вторая строка кода добавляет таймер к текущему циклу выполнения, используя режим .commonModes. Это сообщает циклу выполнения, что таймер должен быть проверен для всех режимов ввода, что позволяет таймеру срабатывать во время взаимодействия с пользовательским интерфейсом.
Выполнение кода с задержкой
Распространенным сценарием в разработке под iOS является выполнение кода с небольшой задержкой. Для этой цели проще всего использовать Grand Central Dispatch, а не Timer.
Следующий код выполняет задачу в основном потоке с задержкой 300 миллисекунд:
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) { print("Привет!") }