Avatar ·

Как использовать dispatch_after в Swift 3, 4 и 5 для отложенного выполнения?

Привет! Я новичок в Swift и пытаюсь перенести старый код с Objective-C, где я использовал dispatch_after для задержки выполнения. Раньше я писал что-то вроде: ```objective-c dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"Прошло 2 секунды"); }); ``` Но в Swift 3 и выше это не работает. Я пробовал писать так: ```swift dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2.0 * Double(NSEC_PER_SEC))), DispatchQueue.main, { print("Прошло 2 секунды") }) ``` Но Xcode выдаёт ошибку: "Use of unresolved identifier 'dispatch_after'". Я понимаю, что GCD сильно изменился, но не могу найти понятного примера для Swift 3/4/5. Пожалуйста, объясните, как правильно сделать задержку на 2 секунды с выполнением на главном потоке. Мне нужен простой рабочий пример, чтобы я мог вставить его в свой код. Заранее спасибо! <<>>

Правильный способ отложенного выполнения в Swift 3+

В Swift 3 API Grand Central Dispatch (GCD) был полностью переработан. Вместо старого C-стиля dispatch_after теперь используется современный синтаксис с методами asyncAfter у DispatchQueue. Вот как это выглядит:

// Базовая задержка на 2 секунды на главном потоке
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
    print("Прошло 2 секунды")
}

Объяснение синтаксиса

  • DispatchQueue.main — очередь, на которой будет выполняться код (главный поток для UI).
  • asyncAfter(deadline: .now() + 2.0) — метод, который запускает замыкание через указанное время.
  • .now() — текущий момент времени.
  • + 2.0 — добавляем 2 секунды к текущему времени (можно использовать Double, Int или DispatchTimeInterval).

Примеры для разных сценариев

1. Задержка на главном потоке (для обновления UI)

DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
    self.label.text = "Текст появился через 3 секунды"
}

2. Задержка на фоновой очереди

DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 1.5) {
    // Тяжелая работа в фоне
    let result = self.performHeavyTask()
    DispatchQueue.main.async {
        // Возвращаем результат на главный поток
        self.updateUI(with: result)
    }
}

3. Использование DispatchTimeInterval для наглядности

let delay = DispatchTimeInterval.seconds(2)
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
    print("Задержка 2 секунды через DispatchTimeInterval")
}

// Или для миллисекунд:
let msDelay = DispatchTimeInterval.milliseconds(500)
DispatchQueue.main.asyncAfter(deadline: .now() + msDelay) {
    print("Задержка 500 мс")
}

4. Отмена запланированной задачи (если нужно)

Для отмены используйте DispatchWorkItem:

let workItem = DispatchWorkItem {
    print("Этот блок может быть отменён")
}

// Запланировать выполнение через 5 секунд
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0, execute: workItem)

// Если передумали — отменяем
workItem.cancel()

Почему это работает во всех версиях Swift 3, 4, 5?

API asyncAfter был введён в Swift 3 и остаётся неизменным в Swift 4 и 5. Единственное, что могло измениться — это синтаксис замыканий (trailing closure), но пример выше совместим со всеми версиями. Если вы используете Swift 5.5+ и iOS 13+, можно также рассмотреть Task.sleep из async/await, но GCD остаётся стандартным решением для простых задержек.

Полный рабочий пример для копирования

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Задержка 2 секунды, затем обновление UI
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
            guard let self = self else { return }
            self.view.backgroundColor = .red
            print("Фон стал красным через 2 секунды")
        }
    }
}

Не забудьте использовать [weak self] внутри замыкания, если оно захватывает self, чтобы избежать retain cycle.

<<>>

Альтернативный подход: использование Timer

Если вам нужно выполнить однократное действие с задержкой и при этом иметь возможность отменить его, можно использовать Timer (ранее NSTimer). Этот способ может быть удобнее, если вы уже работаете с таймерами в проекте:

Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { timer in
    print("Сработал таймер через 2 секунды")
    // Здесь можно обновить UI
}

Преимущества Timer:

  • Простая отмена через timer.invalidate()
  • Легко превратить в повторяющийся (repeats: true)
  • Работает на главном потоке по умолчанию

Недостатки:

  • Таймер менее точен при больших задержках (может "плавать" на загруженном RunLoop)
  • Требует явного управления жизненным циклом (слабая ссылка на self)

Для Swift 5.5+ (iOS 13+) — async/await

Если ваш проект использует современный Swift Concurrency, можно сделать задержку через Task:

Task {
    try? await Task.sleep(nanoseconds: 2_000_000_000) // 2 секунды
    print("Прошло 2 секунды (async/await)")
}

Этот способ наиболее читаемый, но требует iOS 13+ и Swift 5.5. Для обратной совместимости GCD остаётся лучшим выбором.