Как правильно использовать dispatch_sync, dispatch_async, dispatch_after и другие GCD функции в Swift 3, 4 и новее?
Полное руководство по 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 остаётся более лёгким и быстрым решением. Выбирайте инструмент под задачу.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги