> Как работает таймер в iOS и с чем связана его работа (iOS, Swift)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Совкомбанк

Стек: iOS, Swift

> Пример ответа

Короткий ответ

Таймер в iOS - это объект Timer, который планирует выполнение блока или метода через заданный интервал. Его работа связана с run loop: таймер регистрируется в определённом режиме (default, common, tracking) и срабатывает, когда run loop обрабатывает события. Важно: таймер не гарантирует точное время при высокой нагрузке на главный поток, так как он зависит от доступности run loop. Для фоновых задач используют DispatchSourceTimer или Task.sleep.

Подробное объяснение

Timer в Foundation - это обёртка над CFRunLoopTimer. При создании таймер добавляется в текущий run loop (обычно главный) в указанном режиме. Run loop проверяет таймеры на каждой итерации: если fireDate наступил и режим активен, вызывается обработчик.

Ключевые аспекты:

  • Режимы run loop: default - обычная работа, tracking - во время скролла, common - объединяет несколько режимов. Если таймер добавлен в default, он не сработает во время отслеживания жестов (например, при скролле), если явно не добавить его в common.
  • Задержки: таймер срабатывает не точно в момент fireDate, а при ближайшей итерации run loop. Если главный поток занят длительной синхронной задачей, таймер "опаздывает" и может пропустить несколько срабатываний (для повторяющихся таймеров).
  • Retain cycle: run loop удерживает таймер, таймер удерживает target (если используется selector-based API). Поэтому нужно вызывать invalidate() для остановки и освобождения.
  • Альтернативы:
    • DispatchSourceTimer - работает на GCD, не зависит от run loop, точнее и эффективнее для высокочастотных задач.
    • Task.sleep в async/await - для современных асинхронных сценариев, не блокирует поток.
    • CADisplayLink - для синхронизации с частотой обновления экрана (анимации).

На практике

  • Для UI-обновлений используй Timer на главном потоке с режимом .common, чтобы таймер работал во время скролла.
  • Для фоновых периодических задач (например, обновление данных) предпочтительнее DispatchSourceTimer - он не зависит от run loop и работает в фоновой очереди.
  • Всегда сохраняй ссылку на таймер и вызывай invalidate() в deinit или при уходе с экрана, иначе будет утечка памяти.
  • Для точных интервалов (например, игровой цикл) используй CADisplayLink или DispatchSourceTimer с учётом времени последнего срабатывания, так как Timer может "дрейфовать".

Пример кода

SWIFT
final class TimerExample {
private var timer: Timer?
func startTimer() {
// Режим .common - работает во время скролла
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] timer in
self?.updateUI()
}
// Добавляем в common режим, если создавали через timerWithTimeInterval
RunLoop.main.add(timer!, forMode: .common)
}
func stopTimer() {
timer?.invalidate()
timer = nil
}
private func updateUI() {
// обновление интерфейса
}
deinit {
timer?.invalidate()
}
}
// Альтернатива на GCD
final class DispatchTimerExample {
private var timer: DispatchSourceTimer?
func startTimer() {
let queue = DispatchQueue.global(qos: .background)
timer = DispatchSource.makeTimerSource(queue: queue)
timer?.schedule(deadline: .now(), repeating: 1.0, leeway: .milliseconds(100))
timer?.setEventHandler { [weak self] in
self?.doWork()
}
timer?.resume()
}
func stopTimer() {
timer?.cancel()
timer = nil
}
}

Как отвечать на собеседовании

Начни с определения: Timer - это обёртка над CFRunLoopTimer, работает через run loop. Затем объясни связь с run loop: таймер срабатывает только когда run loop активен и находится в нужном режиме. Упомяни, что точность не гарантируется при блокировке главного потока. После этого перечисли альтернативы и их преимущества: DispatchSourceTimer для фоновых задач, CADisplayLink для анимаций, Task.sleep для async/await. Если спросят про утечки - объясни retain cycle и необходимость invalidate(). Для senior-уровня добавь рассуждение о том, когда какой инструмент выбирать: Timer для простых UI-задач, GCD-таймер для критичных по времени операций.

Что проверяет интервьюер

  • Понимание устройства run loop и его режимов.
  • Осознание ограничений Timer (неточность, зависимость от главного потока).
  • Знание альтернатив и умение обосновать выбор.
  • Внимание к управлению памятью (retain cycle, invalidate).
  • Понимание разницы между Timer и DispatchSourceTimer в контексте потоков и планирования.

Типичные ошибки

  • Утверждение, что Timer работает точно по времени - это не так, он зависит от run loop.
  • Забывают про режим .common, из-за чего таймер не срабатывает во время скролла.
  • Не вызывают invalidate(), что приводит к утечке памяти.
  • Используют Timer в фоновых потоках без явного добавления в run loop этого потока - таймер не сработает.
  • Путают Timer с DispatchSourceTimer, считая их взаимозаменяемыми без учёта контекста.
  • Не учитывают, что повторяющийся Timer может "схлопываться" при задержках - он пропускает пропущенные срабатывания, а не нагоняет их.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью