Как сделать задержку (delay) в Swift: полное руководство
Правильный способ создания задержки в 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
}
Это особенно полезно в играх, таймерах или при загрузке данных, когда пользователь может передумать. Всегда проверяйте отмену задачи, чтобы избежать неожиданного поведения.