Параллелизм

Как выполнять параллельные асинхронные операции с помощью DispatchGroup

Узнайте, как запустить несколько параллельных URL-запросов как одну операцию

Вы когда-нибудь хотели вызвать несколько конечных точек API одновременно и получать уведомление, когда все они будут завершены? Возможно, вы хотите выполнять асинхронные операции в пакетном режиме и знать, когда все операции в пакете завершены.

Есть много причин, по которым вы можете захотеть выполнить несколько асинхронных операций параллельно. В этом руководстве мы рассмотрим параллельное выполнение нескольких запросов URL как одну операцию.

В этом посте мы узнаем, как использовать DispatchGroup, чтобы получать уведомления о завершении нескольких одновременных асинхронных операций. Я предполагаю, что вы знакомы с основами Swift и Grand Central Dispatch.

Для этого сообщения я использовал Swift 5.2.4 и Xcode 11.5.

Установка контекста

Допустим, у вас есть приложение для торговых точек. В этом приложении пользователь может создать продукт для продажи в магазине. Пользователь также может предоставить CSV-файл с продуктами для создания.

Когда пользователь создает продукт через интерфейс приложения, приложение вызывает конечную точку API серверной части POS POST /product для создания единого продукта.

Однако, когда пользователь решает создать несколько продуктов, нам необходимо вызвать конечную точку для каждого продукта, указанного в CSV-файле.

Итак, как мы можем легко вызвать POST /product конечную точку для каждого продукта в CSV и получить уведомление о завершении их всех? Ответ - с помощью DispatchGroup!

Группа отправки позволяет группировать несколько асинхронных операций и получать уведомления, когда все они будут завершены.

Как выполнять параллельные запросы API с помощью Dispatch Group

В этом разделе мы реализуем функцию параллельного запроса API в уже существующем приложении POS. Будут предоставлены POS-приложение и сервер.

Вот план этого раздела:

  1. Получить стартовый проект
  2. Запустить сервер
  3. Выполнять параллельные запросы API
  4. Используйте DispatchGroup, чтобы получать уведомления о завершении запросов API

1. Получить начальный проект.

Начнем с загрузки стартового пакета. Откройте терминал и выполните следующие команды:

cd $HOME
curl https://github.com/anuragajwani/dispatch-group-demo/archive/starter.zip -o dispatch_group_demo.zip -L -s
unzip -q dispatch_group_demo.zip

2. Запустить сервер

Начальный проект содержит сервер для запуска, который станет API, с которым наше приложение будет взаимодействовать и запрашивать при создании продуктов.

Для запуска сервера выполните следующие команды:

cd ~/dispatch-group-demo-starter/POSBackEnd
swift build
./.build/debug/POSBackEnd

При первом запуске команд запуск сервера может занять некоторое время. В первый раз команда получит зависимости, необходимые для запуска сервера.

Для этого проекта я подключил сервер Swift, который будет выступать в роли API продуктов. Поскольку он будет работать на наших соответствующих машинах, мы легко сможем общаться с ним с помощью симулятора. Однако учтите, что это не будет работать с устройствами iOS.

Также обратите внимание, что закрытие этого окна терминала приведет к остановке работающего сервера. Убедитесь, что сервер все время работает во время публикации этого сообщения.

3. Выполняйте параллельные запросы API.

Стартовый проект также содержит приложение для iOS. Приложение уже способно создать единый продукт.

В этом разделе мы заполним uploadProductsCSV и добавим код для параллельного создания нескольких продуктов.

Сначала давайте откроем проект приложения. В новом окне терминала выполните следующую команду:

open -a Xcode ~/dispatch-group-demo-starter/POSDispatchGroupDemo/POSDispatchGroupDemo.xcodeproj

Далее мы прочитаем файл с товарами в формате CSV. Файл включен в приложение. Мы прочитаем products.csv файл и преобразуем продукты из CSV в Product в функции, выполняемой, когда пользователь нажимает кнопку Create products from products.csv; theuploadProductsCSV функция в ViewController.swift. Я уже включил для этого вспомогательную функцию getProductsFromCSV. Таким образом, нам нужно только вызвать функцию и сохранить продукты в новой переменной.

Откройте ViewController.swift и добавьте следующую строку кода в uploadProductsCSV:

let products = self.getProductsFromCSV()

Затем мы возьмем каждый продукт из products и создадим POST /product запрос. Я уже создал удобную функцию, которая выполняет запрос. От нас потребуется только позвонить. Добавьте следующие строки кода:

products.forEach({ product in
    self.createProduct(product, onCompletion: { _ in
        // TODO handle completed product creation
    })
})

Обратите внимание, что в этом посте мы игнорируем любые ошибки запроса. CSV содержит действительные продукты, и сервер работает локально, поэтому при обмене данными с сервером не должно быть никаких ошибок.

У вас должна быть возможность запустить приложение и создать все продукты в файле products.csv. Однако у нас пока нет механизма, чтобы уведомить нас о завершении всех запросов API.

4. Используйте DispatchGroup, чтобы получать уведомления о завершении запросов API.

Затем мы воспользуемся DispatchGroup, чтобы получать уведомления о завершении всех запросов API.

Сначала давайте создадим экземпляр DispatchGroup в начале функции. Добавьте следующую строку кода:

let dispatchGroup = DispatchGroup()

Далее перед каждым вызовом createProduct нам нужно добавить add dispatchGroup.enter(). При каждом завершении createProduct мы должны вызывать dispatchGroup.leave(). Функция uploadProductsCSV должна выглядеть так:

@IBAction func uploadProductsCSV(_ sender: Any) {
    let dispatchGroup = DispatchGroup()
    let products = self.getProductsFromCSV()
    products.forEach({ product in
        dispatchGroup.enter()
        self.createProduct(product, onCompletion: { _ in
            dispatchGroup.leave()
        })
    })
}

dispatchGroup.enter() уведомляет группу диспетчеризации о новом асинхронном вызове. dispatchGroup.leave() уведомляет группу диспетчеризации о завершении асинхронного вызова.

Затем мы зарегистрируем закрытие в группе отправки, которое будет выполнено после завершения всех асинхронных операций. В конце uploadProductsCSV добавьте следующую строку кода:

dispatchGroup.notify(queue: .main, execute: { self.showProductsCreatedAlert() })

Важно, что notify вызывается после, когда мы сообщаем диспетчерской группе обо всех выполненных асинхронных вызовах. Если мы зарегистрируем закрытие до того, как мы уведомим диспетчерскую группу об асинхронных вызовах, которые должны быть выполнены, закрытие будет вызвано немедленно. Почему? Группа диспетчеризации вызывает закрытие, когда счетчик выполняемых в данный момент асинхронных вызовов достигает 0. Если мы зарегистрируем закрытие до того, как мы сообщим диспетчерской группе о выполненных асинхронных вызовах, счетчик будет фактически равен 0, и группа диспетчеризации выполнит закрытие с уведомлением.

Для каждого продукта, отправленного на сервер, счетчик увеличивается на 1 (когда мы вызываем dispatchGroup.enter()). Счетчик уменьшится на 1, когда мы закончим отправку продукта (когда мы позвоним dispatchGroup.leave()). Когда счетчик достигнет 0, диспетчерская группа выполнит зарегистрированное закрытие (то, которое мы передаем dispatchGroup.notify(queue:_, execute:_)).

Вот и все! Запустите приложение POSDispatchGroupDemo на симуляторе, нажмите кнопку Create products from products.csv и посмотрите, как оно работает!

В CSV-файле 5 товаров. Это iPhone, которые продаются на момент написания статьи на веб-сайте Apple, и их цены в Великобритании. Вы можете увидеть создаваемые продукты в окне терминала, на котором запущен сервер.

Резюме

В этом посте мы научились выполнять параллельные асинхронные операции и получать уведомления о завершении всех асинхронных операций.

Вы можете найти полный исходный код в репозитории ниже:



Заключительные примечания

DispatchGroup - отличный простой инструмент для использования, когда вы хотите быстро выполнить пакетную обработку нескольких асинхронных операций. Однако в настоящее время DispatchGroup не предлагает никаких готовых механизмов для уведомления нас, когда одна операция завершена и сколько осталось.

Есть также много других способов группировки асинхронных операций и получения уведомлений о завершении каждой отдельной операции, а также всего. Одним из примеров такого решения является использование фреймворка реактивного программирования.

Следите за новостями о разработке для iOS! Подписывайтесь на меня в Twitter или Medium!