Dispatch после Swift: GCD для 3, 4 и 5 на примерах

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как перестать бояться задержек и полюбить GCD в Swift 3, 4 и 5

Вы когда-нибудь писали код, который должен был выполниться через три секунды, но срабатывал мгновенно? Или, наоборот, ждали целую вечность, хотя планировали пару миллисекунд? Знакомо? Тогда вы точно сталкивались с dispatch_after — одним из самых популярных, но при этом самых коварных инструментов Grand Central Dispatch (GCD).

Раньше, в эпоху Swift 2 и Objective-C, этот метод выглядел как заклинание: dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(nanoseconds)), dispatch_get_main_queue()). Но с приходом Swift 3, 4 и 5 API GCD полностью переписали. Теперь это не просто смена названия — это новый способ мышления. В этой статье мы разберем, как писать dispatch_after в Swift на всех актуальных версиях, развеем мифы и дадим готовые решения. Поехали!

Что случилось с dispatch_after? Краткая история боли

До Swift 3 разработчики использовали C-стиль GCD:

let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(2.0 * Double(NSEC_PER_SEC)))
dispatch_after(delay, dispatch_get_main_queue()) {
    print("Привет из прошлого!")
}

Это работало, но выглядело громоздко. В Swift 3 Apple решила "свифтизировать" GCD, превратив его в объектно-ориентированный API. Теперь dispatch_after превратился в метод asyncAfter у структуры DispatchQueue. И это не просто косметический ремонт — это полностью новый синтаксис, который требует понимания.

Основной рецепт: asyncAfter в Swift 3, 4 и 5

Независимо от версии Swift (3, 4 или 5), базовый синтаксис dispatch_after теперь выглядит одинаково. Вот самый простой пример отложенного выполнения на главном потоке:

let delayInSeconds: Double = 2.0
DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) {
    print("Прошло 2 секунды!")
}

Ключевые моменты:

  • DispatchQueue.main — основной поток (UI).
  • .now() + delayInSeconds — создаем deadline (крайний срок) относительно текущего момента.
  • asyncAfter — асинхронное выполнение после указанного времени.

Этот код работает в Swift 3.0, 4.0 и 5.0 без изменений. Магия в том, что Apple оставила этот API стабильным после 2016 года.

А как же dispatch_time? Работа с наносекундами

Иногда вам нужно указать задержку в наносекундах (например, для точных таймеров или игровой логики). В новом API используется DispatchTime:

let nanoseconds: Int = 1_500_000_000 // 1.5 секунды
let deadline = DispatchTime.now() + .nanoseconds(nanoseconds)
DispatchQueue.global().asyncAfter(deadline: deadline) {
    print("Выполнено через 1.5 секунды в фоновом потоке")
}

Вы также можете использовать готовые константы: .seconds(5), .milliseconds(500) и даже .microseconds(1000). Это удобнее, чем умножать на NSEC_PER_SEC.

Частые вопросы (и ответы!) про dispatch_after в Swift

На Stack Overflow этот вопрос собрал тысячи просмотров. Вот самые популярные грабли, на которые наступают новички.

1. Почему мой код выполняется немедленно, а не с задержкой?

Самая распространенная ошибка — путаница между asyncAfter и async. Если вы забыли указать deadline: или написали DispatchQueue.main.async { ... }, код выполнится сразу. Вторая причина: передача отрицательного значения. .now() + (-5.0) — это прошедшее время, GCD выполнит блок немедленно.

2. Как отменить dispatch_after?

В отличие от Timer, у asyncAfter нет встроенного метода cancel(). Но есть два решения:

  • Флаг отмены: Используйте DispatchWorkItem.
  • Таймер: Если нужна отменяемая задержка, используйте Timer.scheduledTimer(withTimeInterval:repeats:block:).

Пример с DispatchWorkItem:

let workItem = DispatchWorkItem {
    print("Можно отменить!")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0, execute: workItem)

// Если передумали:
workItem.cancel()

3. Работает ли dispatch_after в SwiftUI или Combine?

Да, GCD — это низкоуровневый API, который работает везде. Но если вы используете SwiftUI, рассмотрите более "свифтовый" подход: Task.sleep(nanoseconds:) из async/await (Swift 5.5+). Вот пример:

func delayedTask() async {
    do {
        try await Task.sleep(nanoseconds: 2_000_000_000) // 2 секунды
        print("Это async/await!")
    } catch {
        print("Задача прервана")
    }
}

Однако для обратной совместимости (iOS 12 и ниже) dispatch_after через GCD остается лучшим выбором.

4. Как сделать повторяющуюся задержку (таймер)?

Многие пытаются вызывать asyncAfter рекурсивно. Это работает, но лучше использовать DispatchSourceTimer или Timer:

let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
timer.schedule(deadline: .now(), repeating: .seconds(5))
timer.setEventHandler {
    print("Повторение каждые 5 секунд")
}
timer.activate()

Миграция с Objective-C на Swift: пошаговое руководство

Если вы переносите старый код на Objective-C, вот таблица соответствия:

Objective-C (старый) Swift 3/4/5 (новый)
dispatch_after(..., dispatch_get_main_queue()) DispatchQueue.main.asyncAfter(...)
dispatch_time(DISPATCH_TIME_NOW, ...) DispatchTime.now() + .seconds(...)
dispatch_get_global_queue(0, 0) DispatchQueue.global()

Совет: не пытайтесь смешивать старый C-стиль с новым Swift-стилем в одном проекте — это приведет к путанице.

Практический пример: анимация с задержкой

Давайте напишем

Блоги

Рекомендаций по книгам