Очереди задач в Swift: практическое руководство

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

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

Начать курс

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

В современной разработке под iOS и macOS скорость отклика интерфейса — ключевой фактор успеха приложения. Каждый раз, когда вы выполняете тяжёлую операцию (загрузку изображения, парсинг JSON, работу с базой данных) на главном потоке, пользователь видит "подвисание" интерфейса. Решение — очереди задач. В этой статье мы разберём три основных подхода к работе с очередями в Swift: классический GCD, высокоуровневый OperationQueue и современный async/await. Вы получите готовые к использованию примеры кода и узнаете, какой подход выбрать под конкретную задачу.

1. Grand Central Dispatch (GCD): быстрый старт с DispatchQueue

Grand Central Dispatch — это низкоуровневая библиотека для управления параллельным выполнением кода. Она лежит в основе многопоточности в Swift и по-прежнему остаётся самым производительным инструментом для простых фоновых задач.

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

  • main — серийная очередь главного потока (все обновления UI только здесь).
  • global(qos:) — параллельные очереди с приоритетами (userInteractive, userInitiated, utility, background).
  • custom(label:) — ваша собственная очередь (может быть серийной или параллельной).

Пример: загрузка изображения и обновление UI

import UIKit

class ImageLoader {
    func loadImage(from url: URL, completion: @escaping (UIImage?) -> Void) {
        DispatchQueue.global(qos: .userInitiated).async {
            guard let data = try? Data(contentsOf: url),
                  let image = UIImage(data: data) else {
                DispatchQueue.main.async {
                    completion(nil)
                }
                return
            }
            DispatchQueue.main.async {
                completion(image)
            }
        }
    }
}

Практический совет: Всегда используйте .userInitiated для задач, которые пользователь ожидает немедленно (например, открытие изображения). Для фоновой синхронизации данных используйте .background.

2. OperationQueue: управление зависимостями и отмена задач

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

Ключевые возможности:

  • Отмена задач через cancel().
  • Зависимости (addDependency) — задача B выполнится только после задачи A.
  • Контроль максимального количества параллельных операций (maxConcurrentOperationCount).

Пример: загрузка нескольких изображений с ограничением параллелизма

import UIKit

class BatchImageDownloader {
    private let queue: OperationQueue = {
        let q = OperationQueue()
        q.maxConcurrentOperationCount = 3 // Одновременно не больше 3 загрузок
        q.qualityOfService = .userInitiated
        return q
    }()
    
    func downloadImages(from urls: [URL], completion: @escaping ([UIImage]) -> Void) {
        var images: [UIImage] = []
        let syncQueue = DispatchQueue(label: "sync.images")
        
        let finishOperation = BlockOperation {
            DispatchQueue.main.async {
                completion(images)
            }
        }
        
        for url in urls {
            let operation = BlockOperation {
                guard let data = try? Data(contentsOf: url),
                      let image = UIImage(data: data) else { return }
                syncQueue.async {
                    images.append(image)
                }
            }
            finishOperation.addDependency(operation)
            queue.addOperation(operation)
        }
        
        queue.addOperation(finishOperation)
    }
    
    func cancelAll() {
        queue.cancelAllOperations()
    }
}

Когда использовать: Если вам нужно отменить загрузку при уходе с экрана или выстроить цепочку из 5+ последовательных операций — OperationQueue ваш выбор.

3. Async/await: современный Swift и читаемый код

С появлением Swift 5.5 и structured concurrency работа с асинхронностью стала интуитивно понятной. Async/await позволяет писать асинхронный код так, как будто он синхронный — без замыканий и вложенных блоков.

Базовый пример:

import Foundation

// Асинхронная функция
func fetchUserData() async throws -> User {
    let url = URL(string: "https://api.example.com/user")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// Вызов в Task
Task {
    do {
        let user = try await fetchUserData()
        DispatchQueue.main.async {
            self.updateUI(with: user)
        }
    } catch {
        print("Ошибка: \(error)")
    }
}

Работа с параллельными задачами (async let):

func loadDashboard() async throws -> Dashboard {
    async let user = fetchUserData()
    async let posts = fetchPosts()
    async let notifications = fetchNotifications()
    
    // Все три запроса выполняются параллельно
    return try await Dashboard(user: user, posts: posts, notifications: notifications)
}

Преимущества:

  • Отсутствие "callback hell" — код читается линейно.
  • Автоматическая обработка ошибок через try/catch.
  • Легко комбинировать параллельные и последовательные задачи.

4. Сравнение подходов: что выбрать для вашего проекта?

Не существует универсального решения — каждый инструмент закрывает свой спектр задач. Вот краткий чек-лист:

Сценарий Рекомендуемый подход
Простая фоновая задача (загрузка файла) GCD (DispatchQueue.global)
Сложная цепочка зависимостей OperationQueue
Новый проект на Swift 5.5+ Async/await + Task

Блоги

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