Введение в очереди задач в Swift
Современные мобильные и десктопные приложения должны быть быстрыми и отзывчивыми. Если вы выполняете тяжелые вычисления или загружаете данные из сети прямо в главном потоке, ваше приложение зависнет, а пользователь просто закроет его. Решение — очереди задач (queues).
В Swift (iOS/macOS) управление асинхронностью строится на трех основных технологиях: Grand Central Dispatch (GCD), Operation Queue и современном синтаксисе async/await. В этой статье мы разберем каждую из них с нуля, покажем примеры кода и объясним, когда что использовать.
1. Grand Central Dispatch (GCD) и DispatchQueue
GCD — это низкоуровневая библиотека Apple для управления параллельным выполнением кода. Основной элемент — DispatchQueue. Очередь выполняет задачи (замыкания) в порядке их добавления.
Типы очередей
- Main Queue — последовательная очередь главного потока. Используется для обновления UI.
- Global Queues — параллельные очереди с разными приоритетами (QoS:
.userInteractive,.userInitiated,.utility,.background). - Custom Queues — ваши собственные очереди (последовательные или параллельные).
Пример: выполнение задачи в фоне
import Foundation
// Асинхронно в фоновой очереди
DispatchQueue.global(qos: .background).async {
// Тяжелая работа (загрузка, обработка данных)
let result = performHeavyTask()
// Возвращаем результат в главный поток
DispatchQueue.main.async {
updateUI(with: result)
}
}
func performHeavyTask() -> String {
Thread.sleep(forTimeInterval: 2) // имитация работы
return "Готово!"
}
func updateUI(with text: String) {
print("UI обновлен: \(text)")
}
Обратите внимание: .async не блокирует вызывающий поток. Если нужно дождаться результата, используйте .sync (но никогда не вызывайте .sync из главного потока — получите deadlock).
Пример: последовательная очередь
let serialQueue = DispatchQueue(label: "com.example.serial")
serialQueue.async {
print("Задача 1")
}
serialQueue.async {
print("Задача 2")
}
// Вывод: Задача 1, затем Задача 2
2. Operation Queue — более гибкий подход
OperationQueue — надстройка над GCD, предоставляющая объектно-ориентированный интерфейс. Вы создаете Operation (или BlockOperation), настраиваете зависимости и добавляете в очередь.
Преимущества перед GCD
- Отмена задач (
cancel()) - Зависимости между задачами
- Мониторинг состояния (isReady, isExecuting, isFinished)
- Максимальное количество параллельных операций
Пример: базовое использование
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 2 // не более 2 задач одновременно
let operation1 = BlockOperation {
print("Загрузка данных...")
Thread.sleep(forTimeInterval: 1)
}
let operation2 = BlockOperation {
print("Обработка данных...")
Thread.sleep(forTimeInterval: 1)
}
// operation2 начнется только после завершения operation1
operation2.addDependency(operation1)
operationQueue.addOperations([operation1, operation2], waitUntilFinished: false)
Пример: отмена операции
let cancelableOp = BlockOperation {
if !cancelableOp.isCancelled {
print("Выполняю важную работу")
}
}
operationQueue.addOperation(cancelableOp)
// Через некоторое время отменяем
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
cancelableOp.cancel()
print("Операция отменена")
}
3. Современный Swift: async/await (Swift 5.5+)
Начиная с Swift 5.5, Apple добавила встроенную поддержку асинхронности через async и await. Это самый читаемый и безопасный способ работы с очередями.
Как это работает
Функция помечается ключевым словом async, а вызов — await. Система сама управляет переключением контекста.
Пример: асинхронная загрузка
import Foundation
// Асинхронная функция
func fetchUserData() async -> String {
// Имитация сетевого запроса
try? await Task.sleep(nanoseconds: 2_000_000_000)
return "Данные пользователя"
}
// Вызов из другого асинхронного контекста
Task {
let data = await fetchUserData()
print("Получено: \(data)")
}
Параллельное выполнение
Task {
async let first = fetchUserData()
async let second = fetchUserData()
let results = await [first, second] // обе задачи выполняются параллельно
print(results)
}
Переключение на главный поток
В отличие от GCD, в async/await не нужно вручную вызывать DispatchQueue.main.async. Если вы находитесь в контексте главного актора, код выполняется на главном потоке автоматически:
@MainActor
func updateUI() {
// Этот код гарантированно выполняется на главном потоке
label.text = "Новый текст"
}
Task {
let data = await fetchUserData()
await updateUI() // автоматически на главном потоке
}
4. Практическая рекомендация: что выбрать?
| Ситуация | Что использовать |
|---|---|
| Простая фоновая задача | GCD (DispatchQueue.global().async) |
| Сложные зависимости задач | OperationQueue |
| Новый проект (iOS 13+/macOS 10.15+) | async/await |
| Поддержка старых версий iOS | GCD или OperationQueue |
Важное замечание: async/await — это не замена GCD, а более высокоуровневая абстракция. Под капотом она все равно использует очереди. Для простых операций GCD остается отличным выбор