Работа с таймерами в программировании на Swift

Таймеры очень удобны в 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("Привет!")
}
Читайте также:
Добавить комментарий

Ваш адрес email не будет опубликован.