Работа с очередями задач в Swift: GCD и OperationQueue

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

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

Начать курс

Введение: Зачем нужны очереди задач в Swift?

В современной разработке под iOS и macOS производительность приложения напрямую зависит от того, насколько эффективно вы управляете потоками. Длительные операции — загрузка данных из сети, обработка изображений, работа с базой данных — не должны блокировать главный поток (Main Thread), отвечающий за интерфейс. Если главный поток занят, пользователь видит "зависший" экран, а система может принудительно завершить приложение.

Очереди задач (Task Queues) решают эту проблему. Они позволяют выполнять код асинхронно, параллельно или последовательно, не блокируя UI. В Swift для этого есть три основных инструмента: Grand Central Dispatch (GCD), OperationQueue и современная модель async/await (Swift Concurrency). В этой статье мы разберем их на практических примерах и выясним, когда что использовать.

1. Grand Central Dispatch (GCD): классический подход

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

1.1. Основные типы очередей в GCD

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

1.2. Пример: загрузка данных в фоне и обновление UI

Допустим, нам нужно загрузить JSON с удаленного сервера и отобразить результат в таблице.

DispatchQueue.global(qos: .userInitiated).async {
    // 1. Фоновая работа: загрузка данных
    guard let url = URL(string: "https://api.example.com/data") else { return }
    let data = try? Data(contentsOf: url)
    
    // 2. Возвращаемся на главный поток для обновления UI
    DispatchQueue.main.async {
        // Обновляем модель данных или таблицу
        self.tableView.reloadData()
    }
}

1.3. Использование DispatchGroup для синхронизации

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

let group = DispatchGroup()
var images: [UIImage] = []

for url in imageURLs {
    group.enter()
    DispatchQueue.global().async {
        if let data = try? Data(contentsOf: url), let image = UIImage(data: data) {
            images.append(image)
        }
        group.leave()
    }
}

group.notify(queue: .main) {
    print("Все изображения загружены: \(images.count)")
    // Обновляем UI
}

2. OperationQueue: более высокий уровень абстракции

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

2.1. Создание и запуск операций

Операции можно создавать через BlockOperation или наследуясь от Operation.

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 завершена")
}

// Устанавливаем зависимость: operation2 начнется только после operation1
operation2.addDependency(operation1)

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

2.2. Отмена операций

В отличие от GCD, операции можно отменять. Это критично, если пользователь ушел с экрана, а загрузка все еще идет.

// Создаем операцию, которая проверяет флаг isCancelled
let cancellableOperation = BlockOperation {
    for i in 0..<100 {
        if cancellableOperation.isCancelled {
            print("Операция отменена на шаге \(i)")
            return
        }
        print("Шаг \(i)")
        sleep(1)
    }
}

queue.addOperation(cancellableOperation)

// Отменяем через 3 секунды
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    cancellableOperation.cancel()
}

3. Swift Concurrency (async/await): новый стандарт

Начиная с iOS 13 (Swift 5.5), Apple рекомендует использовать async/await и Task. Это делает асинхронный код таким же простым, как синхронный, и автоматически управляет потоками.

3.1. Базовый пример с async/await

func fetchData() async throws -> Data {
    let url = URL(string: "https://api.example.com/data")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return data
}

// Вызов из ViewController
Task {
    do {
        let data = try await fetchData()
        // Автоматически возвращаемся на главный поток
        self.label.text = "Данные загружены: \(data.count) байт"
    } catch {
        print("Ошибка: \(error)")
    }
}

3.2. Параллельное выполнение с async let

Если нужно загрузить несколько файлов одновременно, используйте async let.

func loadMultipleImages() async throws -> [UIImage] {
    async let image1 = loadImage(from: url1)
    async let image2 = loadImage(from: url2)
    async let image3 = loadImage(from: url3)
    
    // Все три загрузки выполняются параллельно
    return try await [image1, image2, image3]
}

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
}

3.3. Акторы (Actors) для потокобезопасности

При работе с очередями задач часто возникает проблема гонки данных (data race). Swift Concurrency решает её через actor — потокобезопасный тип.

actor Counter {
    private var value = 0
    
    func increment() {
        value += 1
    }
    
    func getValue() -> Int {
        return value
    }
}

let counter = Counter()
Task {
    await counter.increment()
    print(await counter.getValue())
}

Сравнение подходов: что выбрать?

Кри

Блоги

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