Avatar ·

Как сделать задержку (delay) в Swift: полное руководство

Всем привет! Я новичок в Swift и iOS-разработке. Пишу простое приложение для тренировки — таймер обратного отсчета. Мне нужно, чтобы после нажатия кнопки "Старт" программа подождала 3 секунды, а потом выполнила какое-то действие (например, показала сообщение или запустила анимацию). Я пробовал использовать `sleep(3)`, но это, кажется, блокирует весь интерфейс — приложение зависает на 3 секунды, и кнопки не нажимаются. Это не то, что нужно. Подскажите, пожалуйста, как правильно сделать задержку в Swift, чтобы интерфейс оставался отзывчивым? Желательно с примером кода для iOS (UIKit или SwiftUI). Я слышал про `DispatchQueue.main.asyncAfter`, но не совсем понимаю, как это работает. Вот мой текущий (неправильный) код: ```swift @IBAction func startButtonTapped(_ sender: UIButton) { print("Начинаем задержку...") sleep(3) // Это блокирует UI print("Прошло 3 секунды") // Здесь будет обновление UI } ``` Заранее спасибо за помощь! <<>>

Правильный способ создания задержки в Swift

Проблема вашего кода в том, что sleep() блокирует главный поток (main thread), на котором работает пользовательский интерфейс. Пока поток "спит", приложение не может обрабатывать нажатия кнопок, анимации и другие события. Решение — использовать асинхронную задержку через DispatchQueue или Task (в Swift 5.5+).

1. Базовый способ: DispatchQueue.main.asyncAfter

Это самый популярный и универсальный метод, работающий во всех версиях Swift:

DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
    // Код выполнится через 3 секунды на главном потоке
    print("Прошло 3 секунды")
    // Можно безопасно обновлять UI
    self.statusLabel.text = "Готово!"
}

Как это работает:

  • DispatchQueue.main — это очередь главного потока, где выполняется UI.
  • .now() + 3.0 — указываем время срабатывания: "сейчас + 3 секунды".
  • Замыкание (closure) выполняется асинхронно — поток не блокируется.

2. Для SwiftUI (iOS 13+)

В SwiftUI удобно использовать Task с задержкой:

Button("Старт") {
    Task {
        print("Начинаем задержку...")
        try? await Task.sleep(nanoseconds: 3_000_000_000) // 3 секунды
        print("Прошло 3 секунды")
        // Обновляем состояние
        statusMessage = "Готово!"
    }
}

3. Альтернатива: Timer

Для повторяющихся задержек или обратного отсчёта:

Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { timer in
    print("Таймер сработал")
    self.showAlert()
}

4. Современный подход: async/await (iOS 13+)

func delayedAction() async {
    do {
        try await Task.sleep(until: .now + .seconds(3), clock: .continuous)
        // Обновление UI
        await MainActor.run {
            self.label.text = "Прошло 3 секунды"
        }
    } catch {
        print("Задержка отменена")
    }
}

Что выбрать?

  • Для простых одноразовых задержек — DispatchQueue.main.asyncAfter
  • Для SwiftUI и современных проектов — Task.sleep с async/await
  • Для повторяющихся действий — Timer

Важно: Всегда обновляйте UI на главном потоке. DispatchQueue.main.asyncAfter и MainActor.run гарантируют это. Используйте [weak self] в замыканиях, чтобы избежать утечек памяти:

DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [weak self] in
    guard let self = self else { return }
    self.label.text = "Готово"
}

Теперь ваше приложение будет отзывчивым, а интерфейс не будет зависать во время задержки!

<<>>

Дополнение: как отменить задержку

Иногда нужно отменить запланированное выполнение, например, если пользователь нажал "Стоп". Вот как это сделать:

Для DispatchQueue:

// Сохраняем WorkItem
var delayedWork: DispatchWorkItem?

func startDelay() {
    delayedWork = DispatchWorkItem { [weak self] in
        print("Действие выполнено")
    }
    DispatchQueue.main.asyncAfter(deadline: .now() + 3.0, execute: delayedWork!)
}

func cancelDelay() {
    delayedWork?.cancel()
    delayedWork = nil
}

Для Task (async/await):

var currentTask: Task?

func startDelay() {
    currentTask = Task {
        try? await Task.sleep(nanoseconds: 3_000_000_000)
        // Действие
    }
}

func cancelDelay() {
    currentTask?.cancel()
    currentTask = nil
}

Это особенно полезно в играх, таймерах или при загрузке данных, когда пользователь может передумать. Всегда проверяйте отмену задачи, чтобы избежать неожиданного поведения.