Какие основные способы передачи и выполнения асинхронных задач в iOS?

Ответ

В iOS есть три основных подхода:

  1. Grand Central Dispatch (GCD) — низкоуровневый C-API для управления очередями.

    // Отправка задачи в глобальную фоновую очередь
    DispatchQueue.global(qos: .userInitiated).async {
        // Выполнение тяжелой операции
        let result = processData()
    
        // Возврат в главную очередь для обновления UI
        DispatchQueue.main.async {
            updateUI(with: result)
        }
    }
  2. OperationQueue — высокоуровневая абстракция на основе GCD, поддерживающая зависимости, отмену и приоритеты.

    let queue = OperationQueue()
    queue.maxConcurrentOperationCount = 2
    
    let operation = BlockOperation {
        // Выполнение задачи
    }
    operation.completionBlock = { print("Готово!") }
    
    queue.addOperation(operation)
  3. Swift Concurrency (async/await) — современный подход, представленный в Swift 5.5.

    Task {
        // Асинхронный вызов
        let data = await fetchDataFromNetwork()
        // Автоматически возвращается в MainActor для UI-обновлений
        updateView(with: data)
    }

Когда что использовать:

  • GCD: Для простых фоновых задач и приоритизации (QoS).
  • OperationQueue: Для сложных рабочих процессов с зависимостями между задачами.
  • async/await: Для нового кода, требующего читаемости и интеграции с акторами (Actor).

Ответ 18+ 🔞

Слушай, ну вот реально, в айосе с этим асинхронным пиздецом — три главных пути, и все они ведут в ад, но по-разному. Разберём, пока не взорвалась голова.

Первый дед — Grand Central Dispatch (GCD). Это типа старый, проверенный ворчун, низкоуровневый С-шный апи. Кидаешь задачи в очереди — он их жуёт.

// Швыряем тяжёлую работу куда подальше, в фоновую очередь
DispatchQueue.global(qos: .userInitiated).async {
    // Тут твой код грузит ядро процессора, ебашит сеть или ещё какую дичь
    let result = processData()

    // А потом, сука, ВСЕГДА возвращайся на главную очередь, чтобы UI обновить!
    DispatchQueue.main.async {
        updateUI(with: result)
    }
}

Суть в чём: сам всё контролируешь, но и повеситься на этих вложенных замыканиях — проще простого. Читаемость — ноль ебать.

Второй вариант — OperationQueue. Это уже повыше, абстракция поверх GCD. Тут уже можно строить цепочки, отменять операции, лимиты ставить — красота, блядь.

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2 // Чтоб не спалилось всё сразу, только две параллельно

let operation = BlockOperation {
    // Делаем дело
}
operation.completionBlock = { print("Готово, нахуй!") } // Выполнится, когда всё ок

queue.addOperation(operation)

Идеально, когда у тебя не просто «сделай раз», а целый рабочий процесс, где одна хуйня зависит от другой. Но и boilerplate-а — овердохуища.

И наконец, новое веяние — Swift Concurrency (async/await). Это как будто из будущего прилетело, в Swift 5.5. Читается почти как обычный код, а работает асинхронно. Магия, ёпта.

Task {
    // Просто ждём, когда данные с сети придут. Никаких callback-аддов!
    let data = await fetchDataFromNetwork()
    // И тут тебя автоматом, по-тихому, возвращает на главный поток для UI
    updateView(with: data)
}

Красиво, модно, молодёжно. Но требует поддержки системы и аккуратного обращения с изоляцией данных (эти ваши акторы).

Так когда что брать, чтобы не обосраться?

  • GCD: Берёшь, когда задача проще пареной репы — «отправь на фон, потом вернись». Или когда нужно тонко поиграться с приоритетами (QoS).
  • OperationQueue: Когда план сложный. «Сначала загрузи A, потом обработай B и C, но только после A, а D можно в любой момент отменить». Вот для такого потока — оно.
  • async/await: Для всего нового, что пишешь с нуля. Особенно если код должен быть читаемым и ты не хочешь, чтобы через полгода твой же коллега (или ты сам) хотел тебя ебать в сраку за спагетти из completion-блоков. И да, для работы с акторами — только оно.

Вот и вся философия, блядь. Выбирай по обстановке, а то так и до deadlock’а недалеко.