Доброго времени суток, друзья!
Если вы уже разрабатывали приложения для iOS, то вы, вероятно, уже сталкивались с получением доступа к данным по сети. И для этого вы могли использовать URLSession.
URLSession является хороший инструментом для работы с сетью, но иногда он может становится громоздким в использовании. И вот здесь вам поможет Alamofire!
Alamofire - это сетевая HTTP-библиотека на базе Swift. Он обеспечивает элегантный интерфейс поверх сетевого стека Apple Foundation, который упрощает общие сетевые задачи. Его функции включают в себя методы запроса/ответа (request/ response), JSON и Codable декодирование, аутентификацию и многое другое.
В этом туториале Alamofire вы будете выполнять основные сетевые задачи, включая:
Запрос данных от стороннего RESTful API.
Параметры отправки запроса.
Преобразование ответа в модель данных Swift через протокол Codable.
Заметка
Прежде чем приступить к изучению этого туториала, вы должны иметь представление о работе с сетями HTTP. Некоторое знакомство с сетевыми классами Apple полезно, но не обязательно. Alamofire скрывает детали реализации, но полезно иметь некоторые базовые знания, если вам нужно устранять неполадки сетевых запросов.
Поехали!
Сперва нужно обратить внимание на API, с которым мы будем работать в этом туториале. https://comicvine.gamespot.com/api/ Этот API предоставляет полный доступ к данным структурированного контента супергероев в форматах XML и JSON. Также Вам нужно будет зарегистрироваться в этом сайте, чтобы получить доступ к уникальному ключу API, через который и откроется доступ к контенту.
В документации к API можно обратить внимание на количество ресурсов, на которые можно обращаться. Также предоставляется список полей, которые будут возвращаться в JSON и фильтры, по котором можно будет самостоятельно фильтровать результат запроса.
Полезный лайфхак №1!
Для работы с сетью хочу порекомендовать Вам программу Postman.
Postman – это мощный набор инструментов тестирования API, ставший необходимым для многих разработчиков. Он используется более чем миллионом разработчиков по всему миру, и это число постоянно растет. Благодаря работе с Postman Вы больше начнете понимать работу с запросами данных.
В данном туториале мы не будем останавливаться на разборе его особенностей (напишите в комментариях если нужно будет). Просто покажем результат нашего запроса, где в качестве query params мы установили api_key и format.
Как можно увидеть выше, мы получаем красивый JSON, по которому можно строить модель в приложении.
Полезный лайфхак №2!
Иногда создание модели может стать мучительной из-за большого количества информации в JSON. Благо и эту работу можно упростить! На помощь нам придут сервисы:
Конечно, подобные сервисы облегчают работу, но в любом случае нужно перепроверять сгенерированные модели. Ознакомится с функционалом Вы можете самостоятельно.
Теперь когда у нас есть API и уже готовая модель, можно начинать работу с сетью.
HTTP, REST и JSON
Постараемся вкратце объяснить, что же такое HTTP, REST, JSON и “с чем их едят”.
HTTP - это протокол приложения, используемый для передачи данных с сервера на клиент, такой как веб-браузер или приложение iOS. HTTP определяет несколько методов запроса, которые клиент использует для указания желаемого действия.
- GET: извлекает данные, например веб-страницу, но не изменяет данные на сервере.
- HEAD: идентичен GET, но отправляет только заголовки, а не фактические данные.
- POST: отправляет данные на сервер. Используйте это, например, при заполнении формы и нажатии «Отправить».
- PUT: отправляет данные в указанное место. Используйте это, например, при обновлении профиля пользователя.
- DELETE: Удаляет данные из указанного местоположения.
JSON означает JavaScript Object Notation. Он обеспечивает простой, удобный для чтения и переносимый механизм для передачи данных между системами. JSON имеет ограниченное количество типов данных на выбор: string, bool, array, object/dictionary, int и null(0).
В прежние времена Swift, до Swift 4, вам нужно было использовать класс JSONSerialization для преобразования JSON в объекты данных и наоборот.
Он работал хорошо, и вы все еще можете использовать его сегодня, но теперь есть и лучший способ: Codable. Соответствуя вашим моделям данных Codable, вы получаете почти автоматическое преобразование из JSON в ваши модели данных и обратно.
REST, или REpresentational State Transfer, представляет собой набор правил для разработки согласованных веб-API. REST имеет несколько архитектурных правил, которые обеспечивают соблюдение стандартов, таких как сохранение состояний в запросах, кэширование запросов и предоставление унифицированных интерфейсов. Это облегчает разработчикам приложений интеграцию API в их приложения без необходимости отслеживать состояние данных в запросах.
HTTP, JSON и REST составляют значительную часть веб-сервисов, доступных вам как разработчику. Попытка понять, как работает каждая элемент, может быть ошеломляющей! Вот где приходит Alamofire.
Зачем использовать Alamofire?
Вы можете быть удивлены, почему вы должны использовать Alamofire. Apple уже предоставляет URLSession и другие классы для доступа к контенту через HTTP, так зачем добавлять еще одну зависимость в базу кода?
Короткий ответ заключается в том, что, хотя Alamofire основан на URLSession, он затеняет многие трудности сетевых вызовов, освобождая вас от концентрации на своей бизнес-логике. Вы можете получить доступ к данным в Интернете без особых усилий, и ваш код будет чище и легче для чтения.
Есть несколько основных функций, доступных с Alamofire:
- AF.upload: выгрузка файлов несколькими методами, потоком, файлами или данными.
- AF.download: загрузка файлов или возобновление уже загруженной загрузки.
- AF.request: другие HTTP-запросы, не связанные с передачей файлов.
Эти методы Alamofire являются глобальными, поэтому вам не нужно создавать экземпляр класса для их использования. Базовые элементы Alamofire включают классы и структуры, такие как SessionManager, DataRequest и DataResponse. Однако вам не нужно полностью понимать всю структуру Alamofire, чтобы начать использовать его.
Запрос данных
Метод запроса данных через Alamofire выглядит так:
AF.request(convertible: URLConvertible, method: HTTPMethod, parameters: Parameters?, encoding: ParameterEncoding?, headers: HTTPHeaders?, interceptor: RequestInterceptor?)
Давайте разберем этот метод:
- URLConvertible - Типы, использующие протокол URLConvertible, могут использоваться для создания URL, которые затем могут быть использованы для построения запроса.
- HTTPMethod - Перечисление, представляющее методы HTTP. Всего оно предоставляет 9 методов: CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE.
- Parameters - Словарь [String: Any].
- ParameterEncoding - Тип, используемый для определения того, как набор параметров применяется к URLRequest. Создает URLRequest, кодируя параметры и применяя их к переданному запросу. По умолчанию возвращает экземпляр URLEncoding.methodDependent.
- HTTPHeaders - Словарь [String: String], сохраняющий порядок и нечувствительное к регистру представление заголовков HTTP.
- RequestInterceptor - Тип, обеспечивающий функциональность RequestAdapter и RequestRetrier. RequestAdapter - Тип, который может проверять и, при необходимости, адаптировать URLRequest неким образом. RequestRetrier - Тип, который определяет, следует ли повторять запрос после его выполнения указанным менеджером сеанса и находит ошибку. (RequestInterceptor - незаменимый в проверке валидности токена)
В целом этот метод может принимать и меньше количество параметров, в таком случае будут использоваться значения по умолчанию.
Если вы уже работали с Alamofire, то скорее всего вы часто использовали метод .responseJSON.
Но с приходом Alamofire 5, вместо преобразования ответа в JSON, теперь можно преобразовывать его во внутреннюю модель данных, например Hero.
Всего лишь 3 строчки кода и мы получаем готовую модель. Это очень удобно и просто!
URLRequestConvertible
Хотели бы поделиться еще одной возможностью Alamofire. Это протокол URLRequestConvertible. Он имеет лишь один метод asURLRequest, который безопасно создает URLRequest.
Согласно исходному коду Alamofire, URLRequestConvertible является протоколом. Единственное требование к протоколу состоит в том, что у него есть вычисляемое свойство var, доступное только для чтения, которое является URLRequest:
На основе этого протокола можно создать свой URLRequestBuilder, который будет служить конструктором готового запроса.
Далее выполняем дефолтную реализацию данного протокола.
Сюда мы включили baseURL, поскольку он одинаков во всех вызовах. Это исключит возможность наличия опечатки в этой части URL для одного из вызовов.
Далее выполняем метод asURLRequest.
Теперь мы можем начать создавать URLRequest. Сначала нам понадобится URL. У нас есть baseURL, добавленный выше, поэтому мы можем объединить его с path для каждого случая, чтобы получить полный URL.
Сначала мы создаем изменяемый запрос, используя URL. Он будет меняться, потому что мы объявили это с помощью var, а не let. Это необходимо, чтобы мы могли установить httpMethod в следующей строке.
Затем мы добавляем в запрос дефолтные Headers. Потом мы кодируем любые параметры и добавляем их в запрос. Обычно запросы GET требуют, чтобы параметры в URL были такими, как https://jsonplaceholder.typicode.com/comments?postId=1. Таким образом, мы в этом случае используем URLEncoding.default.
Заметка
Всегда проверяйте документацию по API, чтобы увидеть, какая кодировка ожидается для каждого вызова.
Наконец, мы возвращаем запрос. Вот примерно так у вас должно получится:
Этот “конструктор запроса” вы можете адаптировать к своим вызовам API.
Теперь мы можем создать отдельный энум CharacterProvider, который будет отвечать только за запросы, относящиеся к characters.
CharacterProvider добавляем case showCharacters. И затем подписываем энум под протокол URLRequestBuilder. Мы получим сообщение “Type 'Some' does not conform to protocol 'URLRequestBuilder'”, нажимаем на кнопку “Fix” и исправляем.
Чтобы было легче за основу возьмем полную строку запроса из программы Postman.
https://www.comicvine.com/api/characters?api_key=d57b7d1f67722aa36b43d4df0e73740bbd91b2b0&format=json&limit=10
Именно этот запрос будет создавать кейс showCharacters:
- path возвращаем “characters”
- в этом примере headers у нас не будет, поэтому можно вернуть nil
- устанавливаем parameters (все, что идет после characters), а именно “api_key”, “format”, “limit”
- method этого запроса GET
“limit” отвечает за количество результатов для отображения на странице. Это значение по умолчанию равно 100 и не может превышать это число.
Мы можем добавить ассоциативное значение в кейс showCharacters, так как при разных запросах нам может быть понадобится разное количество результатов. Поэтому добавляем case showCharacters(limit: Int).
Вот как должно у вас получится:
Дальше пришла очередь создавать ServiceProvider (либо как вы наверное уже привыкли NetworkManager) - сетевой слой.
Но перед тем как создать сетевой слой, создадим Result
Тип Result реализован как enum, который имеет два случая: success(T) и failure(Error).
Оба реализованы с использованием обобщений, поэтому они могут иметь ассоциированное значение по вашему выбору, но ошибка должна быть чем-то, что соответствует типу ошибки в Swift. При желании вы можете использовать определенный кастомный тип ошибки, и отслеживать то, что вам нужно, но это не обязательно.
Даже в этом простом сценарии Result предоставляет преимущество - теперь ясно, что мы вернем либо успешные данные (модель), либо ошибку - невозможно получить оба или ни одно из них.
Теперь когда мы реализовали наш кастомный Result, пришло время создать ServiceProvider.
Но ServiceProvider будет у нас с generic, а именно class ServiceProvider
Класс будет хранить в себе лишь 1 функцию-дженерик, которая будет принимать и отдавать только тот тип, который будет отвечать протоколу Codable.
Готовый метод будет выглядеть так:
На этом подготовка работы с сетью окончена. Пришло время получить данные!
Для примера мы сделали простое приложение с 2 экранами:
- ListViewController - UIViewController с collectionView, где будут отображаться все персонажи
- DetailViewController - UIViewController, где будут отображаться детали выбранного персонажа
Настройка collectionView в ListViewController проходит, как всегда, в несколько этапов:
- Создаем массив объектов var heroes = [Hero]()
- Подписываемся под UICollectionViewDataSource
- Выполняем 2 обязательных метода:
- func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
- func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell - Для ячейки мы создали отдельный класс с методом конфигурации ячейки
- Настраиваем размер ячеек в методе: func collectionView (_ collectionView: UICollectionView, макет collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
- Подписываемся под UICollectionViewDelegateFlowLayout
- Настраиваем отступы для ячеек: func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
- И настраиваем нажатие на ячейку: func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
В DetailViewController разместили UIActivityIndicatorView, UIScrollView -> UIView -> StackView -> UIImageView и 2 UILabel: для имени и описания персонажа.
В методе viewDidLoad() выполнили настройку UI экрана.
Перед тем как продолжить, хотелось бы обратить Ваше внимание наши модели и на 2 кастомных методах:
- UIImageView.load(url: URL?)
- String.htmlAttributed
Модели
Благодаря https://app.quicktype.io мы создали 4 модели:
После генерации моделей мы практически ничего не меняли кроме структуры Image, где поменяли типы на URL.
В нашем случае модель подошла практически идеально для наших задач. Но будьте внимательнее. Всегда перепроверяйте результат генерации моделей.
Метод UIImageView.load(url: URL?)
UIImageView предназначен для загрузки только локальных изображений, но, немного поработав, вы можете также загрузить удаленные изображения. Чтобы получить базовое решение, добавьте расширение к UIImageView, которое загружает данные изображения с использованием фонового потока GCD, затем преобразует его в UIImage и загружает его обратно в представление изображения в главном потоке:
Заметка
Имейте в виду, что если вы попытаетесь вызвать этот метод несколько раз на одном и том же изображении - например, если вы прокручиваете таблицу - тогда вы столкнетесь с проблемами, потому что начнется загрузка нескольких изображений.
Поэтому данная реализация загрузки картинок не подойдет для больших проектов.Для этого мы предлагаем использовать библиотеку SDWebImage.
Метод String.htmlAttributed
Если распечатать строку hero?.resultDescription, то можно увидеть что-то подобное:
Конечно, такая строка может заставить любого врасплох, любого, но только не нашего подписчика!
Управлять HTML можно напрямую с помощью собственных компонентов Swift.
На помощь приходит NSAttributedString(data:options:documentAttributes:)
Вот здесь происходит волшебство - если мы дадим команду NSAttributedString использовать необработанный ввод как документ HTML, результатом будет правильно инициализированный объект со всеми возможными атрибутами, непосредственно извлеченными из тегов HTML. Потрясающие! Как раз то, что мы искали.
Для более любопытных есть еще один вариант. Можно вернуть объект, содержащий словарь всех параметров, используемых html-конвертером, для последующего использования (например, может быть полезно выровнять другое содержимое в UIView, которое не является частью HTML-страницы).
Что происходит? Мы расширили поведение объекта String для обработки интересующего нас преобразования.
Во-первых, мы гарантируем, что можем преобразовать нашу строку в данные; если операция по какой-то причине не удалась, мы вернем ноль. В противном случае давайте продолжим конструкцию try, которая, в свою очередь, вернет интересующее нас значение. Конструкция try catch позволяет нам обрабатывать любые исключения и возвращать ноль только в случае ошибок.
Метод инициализации NSAttributedString (data: options: documentAttributes :) принимает двоичное значение строки как объект данных и дополнительный словарь. Этот словарь указывает, что входными данными .documentType является HTML NSAttributedString.DocumentType.html, а также его кодировка .characterEncoding: String.Encoding.utf8.rawValue.
И теперь мы можем конвертировать HTML в NSAttributedString с помощью одного шага:
descriptionLabel.attributedText = hero?.resultDescription?.htmlAttributed
Работа c ServiceProvider
Работа c ServiceProvider
Для начала нужно создать экземпляр класса ServiceProvider, где вместо T указываем энумератор CharacterProvider.
После этого вызываем метод load(service: CharacterProvider, decodeType: (Decodable & Encodable).Protocol, completion: (Result) -> Void)
Дальше все предельно просто:
- service - выбираем .showCharacters и устанавливаем лимит в 30 персонажей
- decodeType - декодируемый тип для декодирования из данных ответа
- раскрываем completion c Result. Напомню, где может быть только 2 результата: success и failure.
- в случае success - заполняем массив heroes и не забываем перезагрузить collectionView, в случае failure - получаем и распечатываем ошибку
Вызов метода load производим в viewDidLoad(). И готово! Собираем проект и любуемся результатом!
Теперь у нас есть простой в использовании “конструктор”, который мы можем настроить. У нас есть полный контроль над его функциональностью и полное понимание его механики.
Надеемся вы усвоили что-нибудь новое из нашей статьи. Ведь мы стараемся давать Вам лишь самые полезную и нужную информацию!
Автор статьи Михаил Цейтлин.