В предыдущей части мы разобрали преимущества работы с async/await по сравнению с GCD. В этой части мы более подробно рассмотрим ключевые слова async и await (и не только). Разберемся в том, как они работают, что означает "неблокирующее ожидание" и самое главное рассмотрим это все на примерах. Swift async/await. Чем он лучше GCD? Swift async/await на примерах Что такое swift async/await Примеры Async/await. Http запрос Async computed property. Загрузка изображения Async let. Одновременная загрузка двух изображений AsyncSequence. Отображение процента загрузки изображения AsyncStream. Перенос логики загрузки изображения Итоги Полезные ссылки Swift async/await - это новая возможность, добавленная в Swift 5.5, которая расширяет функциональность языка путем введения асинхронных функций. Основная особенность асинхронных функций заключается в том, что они могут приостанавливаться без блокировки текущего потока. Вместо блокировки функция передает управление системе, которая решает, чем далее занять поток(и). Таким образом, достигается неблокирующее ожидание. В примерах, которые будут приведены далее, я буду использовать сервис jsonplaceholder. Он предоставляет API для тестирования. С помощью этого сервиса можно отправлять различные запросы и получать шаблонные данные. Первым делом давайте выполним обычный http запрос: Создаем decodable структуру, в которую мы будем преобразовывать полученный JSON. Создаем функцию, которая будет запрашивать массив объектов типа Photo по id альбома с сервера. Помечаем нашу функцию ключевым словом async. Этим мы сообщаем компилятору, что наша функция потенциально может иметь suspension points (точки приостановки). Грубо говоря, пометив функцию как async, мы можем вызывать внутри этой функции другие async функции с помощью await. Также помечаем нашу функцию ключевым словом throws, что позволит языковыми конструкциями do/try/catch обрабатывать ошибки, вызывая нашу функцию. Формируем GET запрос к серверу.  Отправляем наш запрос с помощью нового асинхронного метода URLSession.data.  Чтобы вызвать асинхронную функцию и получить результат в том же месте, воспользуемся ключевым словом await. Метод возвращает кортеж (Data, URLResponse). В идеале, стоило бы проверить response на наличие ошибок, но для компактности и простоты я пропущу этот шаг. Преобразуем полученный JSON в массив структур и вернем полученный результат из функции. Остановимся чуть подробней на пунктах 2 и 4. Вызывая async функцию у URLSession, мы приостанавливаем выполнение нашей функции. Во время приостановки мы не блокируем текущий поток. Вместо этого система нагружает его другими полезными задачами. Система продолжит выполнение getPhotos, когда метод у URLSession завершит свое выполнение. Функция getPhotos не может быть синхронной, так как она должна иметь возможность приостанавливаться и возобновляться. Из-за этого требуется помечать такие функции как async. Благодаря этому ключевому слову компилятор знает, что функция работает с асинхронным контекстом. Можно провести аналогию с throws функциями. Если наша функция может выбросить ошибку, то ее нужно пометить как throws. Вызывать throws функции можно только с помощью try. Это строгие правила языка. Такие же и у ключевых слов async/await. С помощью ключевого слова await мы (и компилятор в нашем лице) разделяем нашу функцию на части (partials). Функция может быть приостановлена между этими partials. Сами по себе они выполняются без прерываний. Система сама планирует и выполняет эти части в определенном порядке. Хоть визуально код до и после await располагается на соседних строчках, в действительности код после await может быть вызван через длительное время, особенно при работе с сетевыми запросами. Кроме того, он может продолжить выполнение вообще на другом потоке. Об этом важно помнить при работе с UI. Ключевым словом async можно помечать так же замыкания, инициализаторы и вычисляемые свойства (computed property). В этом примере мы воспользуемся асинхронным вычисляемым свойством. На его основе реализуем простой класс для загрузки изображений. В инициализаторе ожидаем URL по которому в дальнейшем будем загружать изображение. Изображение будем загружать через вызов computed property. Помечаем геттер ключевым словом async. Это позволит вызывать внутри другие async функции. Еще одно новшество, не относящееся к async/await, заключается в том, что геттер теперь может быть throws (выкидывать ошибки). Вызываем уже знакомый нам метод у URLSession. Только теперь передаем ему не URLRequest, а просто URL. Преобразовываем Data в UIImage и сразу возвращаем ее. Стоит так же отметить, что сеттер не может быть async, это работает только с геттером. При асинхронном геттере компилятор не даст создать даже обычный (не асинхронный) сеттер. Аналогичная ситуация возникает и с ключевым словом throws. Если пометить им геттер, то сеттер создать вообще не получится. Давайте напишем небольшой контроллер и воспользуемся функцией из нашего примера для загрузки данных для изображений и нашим новым объектом чтоб загрузить изображение. Функция из первого примера Метод viewDidLoad - синхронный. Мы не можем использовать await для ожидания выполнения асинхронных функций внутри синхронного метода. Для этого нужно использовать новую сущность - Task. Мы поговорим про нее подробнее в следующих частях. Сейчас же стоит знать, что если мы хотим вызвать async функцию из синхронной, то вызов нужно осуществлять внутри Task. Получаем массив данных для фотографий с помощью нашего метода. С помощью новой сущности и его async computed property image загружаем изображение и в этой же строчке добавляем его нашей imageView. С помощью конструкции do/try/catch мы можем обработать сразу несколько throws функций внутри одного блока. Если обработка ошибок не предусмотрена, то внутри Task можно не использовать do/try/catch, так как замыкание которое мы передаем в Task может выбрасывать ошибки (помечено ключевым словом throws). Внутри нашей таски мы вызываем асинхронный метод getPhotos и с помощью await  ждем результата. Когда функция вернет массив , выполнение продолжится. После мы достаем URL первого изображения и создаем на его основе объект ImageLoader. Далее так же ожидаем выполнения его асинхронного вычисляемого значения image и в заключении присваиваем полученное изображение в imageView.image. Не забываем, что getPhotos и ImageLoader внутри себя так же вызывают асинхронные функции и приостанавливают свое выполнение ожидая результатов. Немного видоизменим предыдущий пример. Будем загружать 2 разных изображения и рендерить из них одно. Поменяем код внутри Task Как и в предыдущем примере, сначала получаем массив данных изображений. Загружаем поочередно два изображения. Для этого просто беру url у первых двух элементов из массива. Рендерим из них одно изображение. Присваиваем полученный результат в imageView. Все отлично работает, но есть один момент. Мы начинаем грузить первое изображение и ждем, пока оно загрузится с помощью await. Только после того, как наше первое изображение загрузилось, мы начинаем загружать второе. Можно ли загружать оба изображения одновременно? Как вы наверное уже догадались - да. Один из способов - воспользоваться новой конструкцией async let. Давайте поправим наш пример. Теперь мы не await'им изображения, а с помощью async let создаем дочерние таски (про дочерние таски поговорим подробнее в одной из следующих частей). Дочерние задачи сразу отправляются на планирование в систему, и система запускает их при первой возможности. Наша функция не прерывается на async let, выполнение идет дальше после этой конструкции. Вызов асинхронной функции с помощью async let не является потенциальной точкой приостановки (suspension point). Ожидаем завершения задач, запущенных на первом шаге. В этом случае мы можем использовать await для ожидания их поочередно, так как вторая задача продолжает выполнение, пока мы ожидаем завершения первой. Таска, созданная с помощью async let, может завершиться до того, как мы решим получить из нее значение. В таком случае, при вызове await, мы сразу же получим его. Также здесь стоит упомянуть, что после ключевого слова await в выражении можно указывать любое количество асинхронных функций. Например, ожидание загрузки двух изображений в нашем примере можно было записать одной строкой: Пользуясь конструкцией async let можно столкнуться с некоторыми не совсем очевидными моментами: Async let нельзя захватывать в escaping замыкания. Данное ограничение ввели по причине того, что структуры, с помощью которых под капотом реализуется async let, могут храниться на стеке. В таком случае логично предположить, что async let можно использовать в не escaping замыканиях, но на момент написания статьи это делать тоже нельзя. Это баг компилятора, когда вы читаете эту статью он возможно уже поправлен, проверить можно тут.  В async функции которые вызываются с помощью async let нельзя передавать переменные (любых типов). При вызове async let переменная person не копируется, а захватывается. Это происходит из-за того, что под капотом строка с async let преобразуется в замыкание, в котором уже вызывается async функция. Внутрь этого замыкания нельзя захватывать переменные. Этот запрет связан уже с предотвращением состояния гонки при работе в асинхронном контексте. Подробнее поговорим об этом в следующих частях. Для устранения ошибки компиляции в примере, достаточно заменить var на let. AsyncSequence - это протокол, с помощью которого можно обрабатывать последовательности асинхронных элементов в цикле. Как реализовать свой объект, имплементирующий AsyncSequence, поговорим чуть позже, а сейчас давайте воспользуемся объектом, который уже подписан на этот протокол. Воспользуемся новой функцией URLSession.bytes (доступна только с 15 iOS),  которая возвращает пару значений типа (URLSession.AsyncBytes, URLResponse). AsyncBytes как раз и имплементирует протокол AsyncSequence. С помощью этого объекта можно обрабатывать данные из запроса побайтово. Давайте дополним наш контроллер лейблом для отображения процента загрузки изображения и реализуем расчет этого процента с помощью новой функции. Пробежимся по всем изменениям контроллера: Добавили лейбл в котором будем отображать процент загрузки. Получаем пару значений типа (URLSession.AsyncBytes, URLResponse) с помощью нового асинхронного метода URLSession.bytes. Создаем переменную, в которой будем хранить все полученные байты. В цикле обрабатываем байты из AsyncBytes. Байты поступают по мере загрузки, поэтому приостанавливаемся каждый раз с помощью await. При получении каждого последующего байта добавляем его в массив. Затем рассчитываем процент загруженных на данный момент байт относительно ожидаемого количества (которое приходит в URLResponse.expectedContentLength) и обновляем текст у лейбла. Из массива байт создаем Data, из Data создаем UIImage и присваиваем в UIImageView.image. Каждая итерация for await _ in цикла - это потенциальная точка приостановки (suspension point). Такой цикл можно представить как просто последовательный набор await вызовов. Чтобы загрузка не была моментальной, мы можем добавить небольшую задержку внутри цикла: И после этого можно будет увидеть следующий результат: В предыдущем примере мы реализовали загрузку изображения с отображением процента прямо в контроллере, хотя ранее мы инкапсулировали логику загрузки в специальный класс ImageLoader. Давайте перенесем в этот класс логику загрузки с прогрессом, и заодно познакомимся с сущностью AsyncStream. С помощью AsyncStream мы можем легко создавать собственные асинхронные последовательности, так как он подписывается под AsyncSequence. Дополним наш класс ImageLoader и параллельно разберемся, как он работает. Создаем новый enum. C помощью него клиенты, использующие загрузку с прогрессом, будут определять текущее состояние. Всего реализуем 2 состояния. case loading(percent: Int) - загрузка еще в процессе, в этот кейс будем передавать текущий процент загрузки.  case loaded(image: UIImage?) - загрузка завершена, в этот кейс будем передавать загруженное изображение. Добавляем новое computed property типа AsyncStream<LoadingState>. С помощью него клиенты нашего класса смогут обрабатывать в for await _ in цикле асинхронную последовательность событий с типом LoadingState.  Создаем AsyncStream. Для инициализации требуется передать замыкание с типом (AsyncStream<Element>.Continuation) -> Void. В continuation будем передавать наши стейты. Замыкание, которое мы передаем в инициализатор для AsyncStream, не асинхронное. Поэтому в нем нельзя await'ить. По этой причине заворачиваем вызов асинхронной функции в Task. Асинхронная функция loadImageWithProgress включает в себя всю логику загрузки из предыдущего примера. AsyncStream создает continuation (в который нужно передавать события). Мы передаем этот continuation в функцию. Внутри функции мы передаем в него события с помощью метода yield. Задержка для наглядности загрузки. В процессе обработки байтов из стрима от URLSession передаем в continuation события о загрузке с текущим процентом. После завершения стрима байтов от URLSession передаем в continuation событие об окончании загрузки с итоговым изображением. Для завершения стрима нужно дернуть метод finish у continuation. Если это не сделать, то клиенты в for await _ in цикле будут бесконечно ожидать последующий событий, которых в нашем случае больше не будет. Теперь будем использовать ImageLoader в контроллере для загрузки изображения с процентом загрузки. Изменения коснуться только метода viewDidLoad. Создаем ImageLoader и сразу запрашиваем наш AsyncStream. В for await _ in цикле обрабатываем события. В случае если событие .loading - отображаем новый процент. Если .loaded - выставляем полученное изображение Как видно из диаграммы, мы преобразуем каждый полученный байт в LoadingState и передаем его в AsyncStream. Этот стрим, в свою очередь, слушается (с помощью for await _ in цикла) в контроллере, и на основе стейтов  соответствующий пользовательский интерфейс.  В этой статье мы рассмотрели несколько основных сущностей и функциональностей языка. И закрепили это все на приближенных к реальности примерах. Но это еще далеко не все, чем обзавелся Swift в версии 5.5. Для полного преисполнения асинхронностью нам еще предстоит разобраться с structurred cuncurrency и actors, которые включают внутри себя множество интересных деталей. Swift evolution. Async/await proposal WWDC 2021. Use async/await with URLSession WWDC 2021. Meet AsyncSequence Swift evolution. Async let proposal  Ментально пожилой iOS разработчик