Современный код для выполнения HTTP запросов в Swift 5 с помощью Combine и применение его в SwiftUI. Часть 1. База данных фильмов.

Выполнение HTTP запросов — это один из самых важных навыков, которые необходимо получить при изучении iOS. В более ранних версиях Swift ( до версии 5) вне зависимости от того, формировали ли вы эти запросы «с нуля» или с использование известного фреймворка Alamofire,  вы в конечном итоге получали сложный и запутанный код с callback типа completionHandler: @escaping(Result<T, APIError>) -> Void.

Появление в Swift 5 нового фреймворка функционального реактивного программирования Combine в сочетании с уже существующими URLSession и Codable предоставляет вам все необходимые инструменты для самостоятельного написания очень компактного кода для выборки данных из интернета.

Мы будем создавать «издателей» Publisher для выборки данных из интернета, на которые в дальнейшем можно будет легко «подписаться» и использовать при проектировании UI как с помощью UIKit, так и с помощью SwiftUI.

В SwiftUI это выглядит более эффектно, если не сказать «фантастически», так как разделение данных и View в SwiftUI осуществляется с помощью ObservableObject классов с @Published свойствами, изменения которых SwiftUI АВТОМАТИЧЕСКИ отслеживает и полностью «перерисовывает» View. В эти ObservableObject классы можно заложить определенную бизнес-логику приложения, так как некоторые из этих  @Published свойств могут напрямую меняться такими «активными» элементами пользовательского интерфейса (UI) как текстовые поля TextField, Picker, Stepper, Toggle и т.д. Другие @Published свойства, напротив, могут быть «пассивными», являясь результатом синхронных и/ или асинхронных преобразований «активных» @Published свойств, но именно они то нас чаще всего и интересуют. Они обычно воспроизводятся в SwiftUI такими пассивными  (UI) элементами, как текст Text, изображение Image, геометрические фигуры и т.д. Зависимость «пассивных» @Published свойств от «активных» @Published свойств очень просто описать с помощью Combine.

Чтобы было понятно, о чём идет речь, приведу конкретные примеры. Сейчас многие сервисы типа базы данных фильмов TMDb или агрегаторов новостей NewsAPI.org и Hacker News предлагают пользователям выбирать различные коллекции фильмов или наборы статей в зависимости от того, что вас интересует. В случае базы данных фильмов  TMDb это могут быть фильмы, которые идут в данный момент в кинотеатрах, или популярные фильмы, или топовые фильмы,  или фильмы, которые скоро появятся на экране. В случае  агрегаторов новостей NewsAPI.org и Hacker News это могут быть последние новости, или новости в какой-нибудь категории — «спорт», «здоровье», «наука», «технологии», «бизнес», или новости от определенного информационного источника «CNN», «ABC news», «Bloomberg» и т.д., или новости, удовлетворяющие какому-то произвольному критерию. Свои желания для сервисов вы обычно «высказывает» в виде Endpoint, который формирует для вас нужный URL.

Так вот, используя фреймворк Combine, вы можете в ObservableObject классах с помощью очень компактного кода (в большинстве случаев не более 10-12 строк) однократно сформировать синхронную и/или асинхронную зависимость списка фильмов или статей (как «пассивных» @Published свойств) от Endpoint (как «активных» @Published свойств) в виде «подписки», которая будет действовать на протяжении всего «жизненного цикла» экземпляра ObservableObject класса. А далее в SwiftUI управлять только тем, что вы хотите увидеть, то есть выбирать нужную вам Endpoint: то ли это будут популярные фильмы, или фильмы, идущие в данный момент на экране,  то ли это будут статьи с последними новостями или статьи в разделе «здоровье». Появление соответствующих фильмов или статей на вашем UI будет обеспечиваться АВТОМАТИЧЕСКИ этими ObservableObject классами и их пассивными @Published свойствами.  В коде SwiftUI у вас никогда не возникнет необходимости  явно запрашивать выборку фильмов или статей, вы будете управлять только тем, ЧТО вы хотите увидеть через Endpoint, вовсе не заботясь о результате, ибо он ВСЕГДА будет правильным и синхронным благодаря работе ObservableObject классов, которые исполняют роль View Model.

Я на трех конкретных примерах покажу, как это работает с помощью применения Combine для формирования HTTP запросов к различным сервисам и использование их в качестве View Model в SwiftUI.

Начнем с разработки приложения для взаимодействия с базой данных фильмов TMDb, а в последующем — приложений для взаимодействия с агрегаторами новостей NewsAPI.org и Hacker News. Во всех трех случаях будет действовать примерно одна и та же схема использования Combine , ибо в приложениях такого рода всегда приходится формировать СПИСКИ фильмов или статей, выбирать сопровождающие их «КАРТИНКИ» (images), ИСКАТЬ в базах данных нужные фильмы или статьи с помощью поисковой строки.

При обращении к сервисам типа базы данных фильмов TMDb или агрегаторов новостей NewsAPI.org и Hacker News могут возникать ошибки, например, связанные с тем, что вы задали неправильный ключ API-key или превысили допустимое количество запросов или еще что-то. Необходимо обрабатывать такого рода ошибки сервиса. Иначе пользователь вашего приложения попадёт в ситуацию, когда вдруг ни с того, ни с сего перестанут обрабатываться какие-либо запросы, оставляя пользователя в полном недоумении с пустым экраном.  Поэтому надо уметь не только выбирать с помощью Combine данные из интернета , но и сообщать об ошибках, которые могут возникнуть при выборке. В этой статье мы уделим внимание обработке ошибок такого рода в Combine.

Читать далее