Работа с очередями задач в Swift: полное руководство для новичков

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в очереди задач в 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 остается отличным выбор

Блоги

Рекомендаций по книгам