Как в GCD дождаться завершения нескольких асинхронных задач?

Ответ

Да, для этого используется DispatchGroup. Он позволяет отслеживать завершение группы задач и выполнить код, когда все они будут выполнены.

Основные шаги:

  1. Создать группу: DispatchGroup()
  2. Войти в группу перед каждой задачей: enter()
  3. Выйти из группы после завершения каждой задачи: leave()
  4. Установить обработчик завершения: notify() или синхронно дождаться: wait()

Пример с асинхронным уведомлением:

let group = DispatchGroup()

// Задача 1
group.enter()
networkService.fetchData { result in
    defer { group.leave() } // Гарантирует выход
    // Обработка результата
}

// Задача 2
group.enter()
databaseService.loadData { result in
    defer { group.leave() }
    // Обработка результата
}

// Действие после всех задач
group.notify(queue: .main) {
    print("Все асинхронные задачи завершены")
    // Обновление UI или продолжение логики
}

Важные моменты:

  • Количество вызовов enter() должно строго соответствовать количеству leave()
  • Используйте defer { group.leave() } для гарантированного выхода
  • Метод wait() блокирует текущий поток — используйте осторожно
  • Для работы с wait() в main thread используйте .async

Ответ 18+ 🔞

Ну, вот смотри, как это работает на самом деле. Есть у тебя, допустим, несколько асинхронных дел — запрос в сеть, чтение из базы, ещё какая-то хуйня. И тебе надо, чтобы после всей этой каши твой код сработал. Не по одному, а когда всё, блядь, наконец-то закончится.

Для этого есть DispatchGroup. Штука, которая считает, сколько задач в работе, и даёт тебе пинок, когда счётчик дошёл до нуля.

Как с ним обращаться, чтобы не обосраться:

  1. Создаёшь группу: DispatchGroup() — это как взять табло в спортзале.
  2. Перед каждой задачей, которую нужно отслеживать, говоришь группе: «Внимание, я зашёл!» — enter().
  3. После того как задача (обычно в колбэке) выполнилась, не забудь крикнуть: «Я вышел!» — leave(). Иначе группа будет ждать тебя вечно, как дурак.
  4. И ставишь ловушку — notify() — которая сработает, когда все, кто зашёл, благополучно вышли.

Вот, смотри, как это выглядит в коде:

let group = DispatchGroup() // Вот твой счётчик, ёпта

// Допустим, первая задача — тащим данные из сети
group.enter() // Зашли в группу
networkService.fetchData { result in
    defer { group.leave() } // Выйдем из группы в любом случае, даже если тут пиздец случится
    // Тут что-то делаем с результатом...
}

// Вторая задача — ковыряемся в базе
group.enter() // Снова зашли
databaseService.loadData { result in
    defer { group.leave() } // И снова гарантированно вышли
    // Обрабатываем данные...
}

// И вот магия: когда все зашедшие выйдут, сработает этот блок
group.notify(queue: .main) {
    print("Всё, блядь, готово! Можно обновлять интерфейс.")
    // Тут уже безопасно лезть в UI
}

А теперь, блядь, важные детали, на которых все обжигаются:

  • Количество enter() и leave() должно быть строго одинаковым. Если leave() будет меньше — зависнешь навечно. Если больше — крейш, пиздец. defer — твой лучший друг, он ставит leave() в самое начало очереди на выход из функции.
  • Есть ещё метод wait() — он просто блокирует текущий поток и ждёт, пока все задачи не завершатся. НИКОГДА, блядь, НИКОГДА не вызывай его на главном потоке (main), иначе приложение просто встанет колом, и пользователь тебе морду набьёт. Если уж очень надо, оберни вызов в DispatchQueue.global().async.
  • Всё это хозяйство идеально подходит, когда тебе надо, скажем, скачать десять картинок и только потом собрать из них коллаж. Или отправить пять запросов к разным сервисам и потом слить результаты в кучу.

Вот и вся магия. Не так страшен чёрт, как его малюют. Главное — не забывай выходить из комнаты, когда дело сделано.