Группы диспетчеризации (Dispatch Groups) в GCD
При работе с асинхронными задачами часто возникает необходимость отслеживать завершение группы связанных операций. Dispatch Groups в Grand Central Dispatch предоставляют элегантный механизм для координации нескольких асинхронных задач и синхронизации кода. Эта статья раскрывает все аспекты работы с группами диспетчеризации в iOS-приложениях.
Содержание:
Основы работы с Dispatch Groups
Dispatch Groups позволяют группировать задачи и получать уведомление, когда все задачи в группе завершены, даже если они выполняются на разных очередях.
Создание и базовое использование
// Создание группы
let group = DispatchGroup()
// Входим в группу перед началом задачи
group.enter()
someAsyncOperation {
// Выполнение задачи
print("Задача 1 завершена")
// Выходим из группы после завершения
group.leave()
}
// Добавление ещё одной задачи
group.enter()
anotherAsyncOperation {
print("Задача 2 завершена")
group.leave()
}
// Уведомление о завершении всех задач
group.notify(queue: .main) {
print("Все задачи завершены")
updateUI()
}
Ключевые принципы:
- Вызов
enter()
увеличивает внутренний счетчик группы - Вызов
leave()
уменьшает счетчик - Когда счетчик достигает нуля, вызывается блок notify
Явное добавление задач в группу
Помимо пары enter()/leave()
, GCD предлагает более удобный способ добавления задач в группу:
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInitiated)
// Добавление задачи напрямую
queue.async(group: group) {
print("Задача 1 выполняется")
sleep(2)
print("Задача 1 завершена")
}
queue.async(group: group) {
print("Задача 2 выполняется")
sleep(1)
print("Задача 2 завершена")
}
group.notify(queue: .main) {
print("Все задачи завершены")
}
Синхронное ожидание завершения группы
Иногда требуется заблокировать текущий поток до завершения всех задач в группе. GCD предоставляет метод wait
для этой цели.
Блокирующее ожидание
let group = DispatchGroup()
let queue = DispatchQueue.global()
// Добавляем задачи в группу
queue.async(group: group) {
sleep(2)
print("Задача 1 завершена")
}
queue.async(group: group) {
sleep(1)
print("Задача 2 завершена")
}
// Блокируем текущий поток до завершения всех задач
group.wait()
print("После ожидания - все задачи завершены")
Ожидание с таймаутом
Для предотвращения бесконечного блокирования можно установить таймаут:
let result = group.wait(timeout: .now() + 3)
switch result {
case .success:
print("Все задачи завершены в течение таймаута")
case .timedOut:
print("Таймаут: не все задачи успели завершиться")
}
Важно: Не используйте wait()
на главной очереди, так как это приведет к блокировке пользовательского интерфейса.
Вложенные группы и сложные зависимости
Dispatch Groups можно комбинировать для создания сложных зависимостей между задачами.
Последовательное выполнение групп задач
let firstGroup = DispatchGroup()
let secondGroup = DispatchGroup()
let queue = DispatchQueue.global()
// Первая группа задач
queue.async(group: firstGroup) {
print("Задача 1.1 выполняется")
sleep(1)
print("Задача 1.1 завершена")
}
queue.async(group: firstGroup) {
print("Задача 1.2 выполняется")
sleep(2)
print("Задача 1.2 завершена")
}
// Вторая группа запускается после завершения первой
firstGroup.notify(queue: queue) {
print("Первая группа задач завершена")
// Задачи второй группы
queue.async(group: secondGroup) {
print("Задача 2.1 выполняется")
sleep(1)
print("Задача 2.1 завершена")
}
queue.async(group: secondGroup) {
print("Задача 2.2 выполняется")
sleep(1)
print("Задача 2.2 завершена")
}
// Уведомление о завершении второй группы
secondGroup.notify(queue: .main) {
print("Все группы задач завершены")
updateFinalUI()
}
}
Параллельное выполнение групп с финальной синхронизацией
let groupA = DispatchGroup()
let groupB = DispatchGroup()
let finalGroup = DispatchGroup()
let queue = DispatchQueue.global()
// Добавляем финальную группу для отслеживания завершения
finalGroup.enter()
finalGroup.enter()
// Задачи группы A
queue.async(group: groupA) {
sleep(2)
print("Группа A: задача завершена")
}
// Уведомление о завершении группы A
groupA.notify(queue: queue) {
print("Группа A полностью завершена")
finalGroup.leave()
}
// Задачи группы B
queue.async(group: groupB) {
sleep(1)
print("Группа B: задача завершена")
}
// Уведомление о завершении группы B
groupB.notify(queue: queue) {
print("Группа B полностью завершена")
finalGroup.leave()
}
// Финальное уведомление, когда обе группы A и B завершены
finalGroup.notify(queue: .main) {
print("Обе группы A и B завершены")
showFinalResult()
}
Практические сценарии использования
Dispatch Groups особенно полезны в ряде типичных сценариев iOS-разработки.
Параллельные сетевые запросы
func loadDashboardData(completion: @escaping (Result<DashboardData, Error>) -> Void) {
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInitiated)
var userData: UserData?
var postsData: [Post]?
var notificationsData: [Notification]?
var error: Error?
// Загрузка данных пользователя
group.enter()
networkService.fetchUserData { result in
defer { group.leave() }
switch result {
case .success(let data):
userData = data
case .failure(let err):
error = err
}
}
// Загрузка постов
group.enter()
networkService.fetchPosts { result in
defer { group.leave() }
switch result {
case .success(let data):
postsData = data
case .failure(let err):
error = err
}
}
// Загрузка уведомлений
group.enter()
networkService.fetchNotifications { result in
defer { group.leave() }
switch result {
case .success(let data):
notificationsData = data
case .failure(let err):
error = err
}
}
// Обработка результатов всех запросов
group.notify(queue: .main) {
if let error = error {
completion(.failure(error))
return
}
guard let user = userData, let posts = postsData, let notifications = notificationsData else {
completion(.failure(DashboardError.incompleteData))
return
}
let dashboardData = DashboardData(user: user, posts: posts, notifications: notifications)
completion(.success(dashboardData))
}
}
Обработка массива данных с ограничением параллельности
func processImages(_ images: [UIImage], completion: @escaping ([UIImage]) -> Void) {
let group = DispatchGroup()
let queue = DispatchQueue(label: "com.myapp.imageProcessing", attributes: .concurrent)
let resultLock = NSLock()
var processedImages = [UIImage?](repeating: nil, count: images.count)
// Обработка каждого изображения в параллельной очереди
for (index, image) in images.enumerated() {
queue.async(group: group) {
let processedImage = self.applyFilter(to: image)
// Безопасное сохранение результата
resultLock.lock()
processedImages[index] = processedImage
resultLock.unlock()
}
}
// Уведомление о завершении всех операций
group.notify(queue: .main) {
let nonNilImages = processedImages.compactMap { $0 }
completion(nonNilImages)
}
}
Не забудьте загрузить приложение iJun в AppStore и подписаться на мой YouTube канал для получения дополнительных материалов по iOS-разработке.