Очереди задач в Swift: GCD, OperationQueue и Swift Concurrency

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

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

Начать курс

Очереди задач для Swift (iOS/macOS): GCD, OperationQueue и Swift Concurrency

Разработка под iOS и macOS требует эффективного управления параллельными задачами. Очереди задач (Task Queues) — фундаментальный механизм для создания отзывчивых и производительных приложений. В Swift существует три основных подхода: классический Grand Central Dispatch (GCD), объектно-ориентированный OperationQueue и современный async/await (Swift Concurrency). В этой статье мы подробно разберём каждый из них с практическими примерами.

1. Grand Central Dispatch (GCD) — DispatchQueue

GCD — это низкоуровневая библиотека на языке C, встроенная в ядро системы. В Swift она представлена классом DispatchQueue. Позволяет выполнять блоки кода последовательно или конкурентно.

Основные типы очередей:

  • Main Queue — последовательная очередь, работающая на главном потоке. Используется для обновления UI.
  • Global Queues — конкурентные очереди с разными приоритетами (QoS: userInteractive, userInitiated, utility, background).
  • Custom Queues — пользовательские очереди (последовательные или конкурентные).

Пример: Асинхронная загрузка данных с обновлением UI

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var imageView: UIImageView!

    func loadImage(from url: URL) {
        // Переключаемся на фоновую очередь
        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
            guard let data = try? Data(contentsOf: url),
                  let image = UIImage(data: data) else { return }

            // Возвращаемся на главный поток для обновления UI
            DispatchQueue.main.async {
                self?.imageView.image = image
            }
        }
    }
}

Работа с семафорами и группами

let group = DispatchGroup()
let queue = DispatchQueue.global()

group.enter()
queue.async {
    print("Задача 1 выполнена")
    group.leave()
}

group.enter()
queue.async {
    print("Задача 2 выполнена")
    group.leave()
}

group.notify(queue: .main) {
    print("Все задачи завершены")
}

2. OperationQueue и Operation

OperationQueue — надстройка над GCD, предоставляющая объектно-ориентированный интерфейс. Каждая задача — экземпляр класса Operation (или его подкласса BlockOperation). Главные преимущества: управление зависимостями, отмена задач и настройка максимального количества параллельных операций.

Пример: Загрузка с зависимостями

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2

let fetchData = BlockOperation {
    print("Загрузка данных...")
}

let processData = BlockOperation {
    print("Обработка данных...")
}

let updateUI = BlockOperation {
    print("Обновление интерфейса")
}

// Устанавливаем зависимости: processData выполнится после fetchData
processData.addDependency(fetchData)
updateUI.addDependency(processData)

queue.addOperations([fetchData, processData, updateUI], waitUntilFinished: false)

Приоритеты и отмена задач

let operation = BlockOperation {
    if Task.isCancelled { return }
    print("Выполняется...")
}
operation.queuePriority = .veryHigh

// Отмена операции
operation.cancel()

3. Swift Concurrency (async/await)

Начиная с Swift 5.5, Apple представила встроенную модель асинхронности. async/await позволяет писать асинхронный код линейно, без замыканий. Основные компоненты: Task, TaskGroup, actor.

Пример: Асинхронная загрузка изображения

import UIKit

class ImageLoader {
    func loadImage(from url: URL) async throws -> UIImage {
        let (data, _) = try await URLSession.shared.data(from: url)
        guard let image = UIImage(data: data) else {
            throw URLError(.badServerResponse)
        }
        return image
    }
}

// Использование в контроллере
Task {
    do {
        let loader = ImageLoader()
        let image = try await loader.loadImage(from: url)
        self.imageView.image = image
    } catch {
        print("Ошибка загрузки: \(error)")
    }
}

Параллельные задачи с TaskGroup

func loadAllImages(urls: [URL]) async throws -> [UIImage] {
    try await withThrowingTaskGroup(of: UIImage.self) { group in
        for url in urls {
            group.addTask {
                let (data, _) = try await URLSession.shared.data(from: url)
                guard let image = UIImage(data: data) else {
                    throw URLError(.badServerResponse)
                }
                return image
            }
        }

        var images: [UIImage] = []
        for try await image in group {
            images.append(image)
        }
        return images
    }
}

Сравнение подход

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