Avatar ·

Как создать задержку (delay) в Swift? Нужен простой и надёжный способ

Всем привет! Я только начал изучать Swift и Xcode, и у меня возникла проблема. Мне нужно сделать паузу в выполнении кода, например, чтобы показать анимацию загрузки на 2 секунды, а потом перейти на другой экран. В других языках, типа JavaScript, я просто использовал setTimeout. Но в Swift я пока не нашёл простого и понятного способа. Я пробовал использовать Thread.sleep(forTimeInterval: 2), но это, кажется, полностью блокирует интерфейс — приложение зависает на 2 секунды, и ничего не работает. Это точно не то, что нужно. Я слышал про GCD (Grand Central Dispatch) и asyncAfter, но синтаксис выглядит сложным. Ещё есть какой-то Timer, но я не уверен, как его правильно использовать для простой задержки. Подскажите, пожалуйста, самый простой и правильный способ сделать задержку в Swift, чтобы не блокировать главный поток (UI). Мне нужно просто выполнить какой-то код через 2 секунды после нажатия на кнопку. Желательно с примером, чтобы я мог скопировать и сразу использовать. Заранее спасибо! Вот пример моего неудачного кода: ```swift // Это неправильно — интерфейс зависает print("Старт") Thread.sleep(forTimeInterval: 2) print("Прошло 2 секунды") // Дальше код не выполняется нормально ``` <<>>

Правильный способ: DispatchQueue.main.asyncAfter

Самый распространённый и рекомендуемый способ создать задержку в Swift — использовать Grand Central Dispatch (GCD) через метод asyncAfter. Этот метод позволяет выполнить код на указанной очереди (например, главной) после заданной задержки, не блокируя поток, на котором вы сейчас находитесь. В отличие от Thread.sleep, интерфейс остаётся отзывчивым.

Вот простой пример, который делает именно то, что вам нужно:

import UIKit

class ViewController: UIViewController {

    @IBAction func buttonTapped(_ sender: UIButton) {
        print("Кнопка нажата. Начинаем задержку...")
        
        // Задержка в 2 секунды на главном потоке
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
            print("Прошло 2 секунды! Выполняем код.")
            // Здесь можно, например, перейти на другой экран
            // self.performSegue(withIdentifier: "showNextScreen", sender: nil)
        }
        
        print("Этот код выполнится сразу, не дожидаясь задержки")
    }
}

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

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

Обратите внимание: код после вызова asyncAfter (в примере — print("Этот код выполнится сразу...")) выполняется немедленно, не дожидаясь задержки. Это ключевое отличие от блокирующего sleep.

Если нужно выполнить задержку НЕ на главном потоке

Иногда нужно выполнить задержку в фоновом потоке, например, перед загрузкой данных из сети:

DispatchQueue.global(qos: .background).async {
    // Тяжёлая работа в фоне
    print("Начинаем фоновую задачу")
    
    // Задержка 3 секунды на этой же фоновой очереди
    DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 3.0) {
        print("Фоновая задача продолжена через 3 секунды")
        // После этого можно вернуться на главный поток для обновления UI
        DispatchQueue.main.async {
            print("Обновляем интерфейс на главном потоке")
        }
    }
}

Важные моменты

  • Не используйте Thread.sleep на главном потоке — это заморозит приложение на время задержки, и система может даже убить его (покажется, что приложение зависло).
  • Задержка не является точной до миллисекунды — GCD гарантирует, что код выполнится не раньше указанного времени, но может выполниться чуть позже, если система занята.
  • Отмена задержки — если нужно отменить запланированное выполнение, можно использовать DispatchWorkItem и метод cancel(). Но для простых случаев asyncAfter более чем достаточно.

Этот подход считается стандартом в Swift и используется в продакшн-приложениях. Он простой, понятный и не блокирует интерфейс.

<<>>

Альтернативный способ: Timer (NSTimer)

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

Вот пример с однократным таймером:

class ViewController: UIViewController {
    
    @IBAction func buttonTapped(_ sender: UIButton) {
        print("Запускаем таймер на 2 секунды...")
        
        // Создаём таймер, который сработает один раз
        Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { timer in
            print("Прошло 2 секунды! Таймер сработал.")
            // Здесь ваш код
        }
        
        print("Код после таймера выполнится сразу")
    }
}

Когда использовать Timer вместо asyncAfter

  • Повторяющиеся действия — если нужно выполнять код с интервалом (например, обновлять таймер обратного отсчёта), Timer с параметром repeats: true будет удобнее.
  • Отмена в любой момент — таймер легко остановить методом invalidate(), сохранив ссылку на него.
  • Более читаемый код — для некоторых разработчиков синтаксис таймера интуитивно понятнее, чем asyncAfter.

Важно: Не забывайте, что таймер должен быть запущен на главном потоке (по умолчанию scheduledTimer так и делает), иначе он может не сработать. Если вы создаёте таймер в фоновом потоке, нужно явно добавить его в RunLoop.

Оба способа (asyncAfter и Timer) корректно работают с UI и не блокируют интерфейс. Выбирайте тот, который вам кажется понятнее под конкретную задачу.