Avatar ·

Как правильно использовать dispatch_sync, dispatch_async, dispatch_after и другие GCD функции в Swift 3, 4 и новее?

Всем привет! Я перехожу с Objective-C на Swift, и у меня возникла путаница с Grand Central Dispatch (GCD) после выхода Swift 3. Раньше я писал что-то вроде: ```swift dispatch_async(dispatch_get_main_queue(), { // обновление UI }) ``` Но теперь Xcode говорит, что это неправильно. Я понимаю, что Apple изменила API, но не могу найти понятного объяснения, как теперь работать с GCD в Swift 3, 4 и новее. Мне нужно понять: 1. Как выполнить код асинхронно на главной очереди (для обновления UI)? 2. Как выполнить код синхронно (dispatch_sync) в фоновой очереди? 3. Как сделать задержку перед выполнением (dispatch_after)? 4. Как создать свою собственную очередь? Я пробовал писать так, но это не работает: ```swift DispatchQueue.main.async { // код } DispatchQueue.global().sync { // код } ``` Пожалуйста, объясните новичку в Swift, как правильно использовать GCD в современном Swift. Желательно с примерами, которые я мог бы скопировать и сразу использовать. Заранее спасибо! <<>>

Полное руководство по GCD в Swift 3, 4 и новее

Действительно, начиная с Swift 3, Apple полностью переработала API Grand Central Dispatch, заменив старые C-функции на более безопасные и удобные методы, привязанные к типам DispatchQueue, DispatchWorkItem и DispatchGroup. Вот как это работает сейчас.

1. Основные очереди

Вместо dispatch_get_main_queue() и dispatch_get_global_queue() используйте свойства структуры DispatchQueue:

// Главная очередь (Main Queue) — для обновления UI
let mainQueue = DispatchQueue.main

// Глобальная фоновая очередь с приоритетом по умолчанию
let backgroundQueue = DispatchQueue.global()

// Глобальная очередь с конкретным Quality of Service (QoS)
let userInteractiveQueue = DispatchQueue.global(qos: .userInteractive)
let utilityQueue = DispatchQueue.global(qos: .utility)
let backgroundQueueLow = DispatchQueue.global(qos: .background)

2. Асинхронное выполнение (dispatch_async)

Самый частый случай — выполнить код в фоне, а затем обновить UI на главной очереди:

DispatchQueue.global(qos: .userInitiated).async {
    // Тяжелая работа в фоне (загрузка данных, обработка)
    let result = performHeavyTask()

    // Возвращаемся на главную очередь для обновления UI
    DispatchQueue.main.async {
        self.label.text = result
        self.activityIndicator.stopAnimating()
    }
}

3. Синхронное выполнение (dispatch_sync)

Синхронное выполнение блокирует текущий поток до завершения задачи. Используйте его осторожно, чтобы не вызвать deadlock:

// Выполнить синхронно на фоновой очереди (но не на главной!)
DispatchQueue.global().sync {
    // Этот код выполнится, но заблокирует текущий поток
    let data = loadFromFile()
    print(data)
}

// ВНИМАНИЕ: Никогда не вызывайте .sync на главной очереди из главного потока!
// Это приведет к deadlock:
// DispatchQueue.main.sync { } // НЕ ДЕЛАЙТЕ ТАК!

4. Задержка выполнения (dispatch_after)

Для задержки используйте метод .asyncAfter(deadline:):

// Задержка на 2 секунды
let deadline = DispatchTime.now() + 2.0
DispatchQueue.main.asyncAfter(deadline: deadline) {
    self.showAlert()
}

// Или более кратко:
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
    self.showAlert()
}

// Для повторяющихся задержек используйте DispatchSourceTimer
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer.schedule(deadline: .now(), repeating: .seconds(5))
timer.setEventHandler {
    print("Прошло 5 секунд")
}
timer.resume()

5. Создание собственной очереди

Вы можете создать последовательную или параллельную очередь:

// Последовательная очередь (Serial) — задачи выполняются по очереди
let serialQueue = DispatchQueue(label: "com.example.serialQueue")

// Параллельная очередь (Concurrent) — задачи могут выполняться одновременно
let concurrentQueue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)

// Использование:
serialQueue.async {
    print("Задача 1")
}
concurrentQueue.async {
    print("Задача 2")
}

6. DispatchGroup — синхронизация нескольких задач

Когда нужно дождаться завершения нескольких асинхронных операций:

let group = DispatchGroup()

group.enter()
fetchData1 { result in
    print("Данные 1 получены")
    group.leave()
}

group.enter()
fetchData2 { result in
    print("Данные 2 получены")
    group.leave()
}

// Уведомление, когда все задачи завершены
group.notify(queue: .main) {
    print("Все данные загружены, обновляем UI")
}

// Или синхронное ожидание (блокирует поток)
group.wait(timeout: .now() + 10.0)

7. DispatchWorkItem — отмена задачи

Если нужно отменить задачу до её выполнения:

let workItem = DispatchWorkItem {
    print("Выполняется работа")
    // Проверка на отмену
    if workItem.isCancelled { return }
    print("Работа завершена")
}

// Запланировать выполнение
DispatchQueue.global().async(execute: workItem)

// Отменить задачу (если она ещё не начала выполняться)
workItem.cancel()

Итоговый пример: загрузка изображения с прогрессом

func loadImage(from url: URL) {
    let queue = DispatchQueue.global(qos: .userInitiated)
    queue.async {
        guard let data = try? Data(contentsOf: url) else { return }
        let image = UIImage(data: data)
        
        DispatchQueue.main.async {
            self.imageView.image = image
            self.progressView.isHidden = true
        }
    }
}

Главное правило: всегда обновляйте UI на главной очереди, а тяжелые задачи выполняйте в фоне. И никогда не вызывайте .sync на главной очереди — это гарантированный deadlock.

<<>>

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

Хотя GCD — это стандартный инструмент для асинхронности, в Swift также доступна более высокоуровневая абстракция — OperationQueue. Она построена поверх GCD, но предоставляет дополнительные возможности: отмена зависимостей, максимальное количество параллельных операций и приоритеты.

Пример с OperationQueue

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2 // Максимум 2 задачи одновременно

let operation1 = BlockOperation {
    print("Задача 1 началась")
    sleep(2)
    print("Задача 1 завершена")
}

let operation2 = BlockOperation {
    print("Задача 2 началась")
    sleep(1)
    print("Задача 2 завершена")
}

// Задача 2 будет выполнена только после завершения задачи 1
operation2.addDependency(operation1)

queue.addOperations([operation1, operation2], waitUntilFinished: false)

// Отмена всех операций
queue.cancelAllOperations()

Когда использовать OperationQueue вместо GCD?

  • Когда нужно управлять зависимостями между задачами (например, загрузить данные → обработать → показать UI).
  • Когда необходимо ограничить количество параллельных задач (например, не более 3 загрузок одновременно).
  • Когда требуется удобная отмена задач, которые ещё не начались.

Совмещение с GCD

Вы можете легко комбинировать оба подхода:

let operation = BlockOperation {
    DispatchQueue.main.async {
        self.updateUI()
    }
}
queue.addOperation(operation)

Помните, что OperationQueue — это надстройка над GCD, поэтому для простых случаев (например, простой async-вызов) GCD остаётся более лёгким и быстрым решением. Выбирайте инструмент под задачу.