Хай! Вам знакомо то чувство уныния, когда нужно интегрировать проект с очередным RESTful API? Это когда в очередной раз нужно создавать какой-нибудь APIManager и наполнять его Alamofire запросами, а потом связывать их с моделями маппинга данных. Лично я стараюсь максимально оптимизировать всю свою работу, поэтому регулярно изучаю различные библиотеки чтобы не писать кучу повторяющегося кода и избавиться от рутины. В один из таких заходов я наткнулся на отличную библиотеку Moya, о которой и пойдёт речь.
На самом деле, на эту библиотеку я натыкался несколько раз и она даже пылилась у меня в закладках браузера, но откладывал её изучение, о чём впоследствии не раз пожалел. Авторы этой библиотеки выложили красноречивую картинку «до» и «после» в своём репозитории:

Впечатляет, правда? Суть библиотеки сводится к тому, что всю сетевую часть можно интегрировать быстро и минимальными телодвижениями — всю низкоуровневую работу за вас сделает Moya.
Создадим Single-View Applicaton и и подключим библиотеку к нашему проекту (для маппинга я предпочитаю библиотеку ObjectMapper, для подключения сторонних зависимостей — CocoaPods)
Далее нам нужно создать файл с запросами, делается это так:
В этом файле происходит настройка запросов. В самом начале мы видим enum — это наш будущий сервис со всеми запросами. Можно запихнуть все запросы в один сервис, но в больших проектах я рекомендую придерживаться буквы I из SOLID и не превращать файл в кашу. После перечисления всех запросов в enum нам нужно расширить класс протоколом
1.
2.
3.
4.
5.
6.
7.
Для того, чтобы начать использовать Moya, нам необходимо создать Provider — это абстракция библиотеки, которая даёт доступ к запросам:
После этого можножениться делать запрос с помощью provider:
Moya поддерживает ReactiveSwift и RxSwift. Лично я предпочитаю последнюю библиотеку, поэтому мой пример будет для неё. Для начала давайте добавим нужные зависимости:
И наш код трансформируется следующим образом:
Обо всех возможностях Moya рассказывать долго, поэтому советую после прочтения этой статьи заглянуть в документацию. А я сейчас покажу несколько вещей, которые могут пригодиться и Вам:
1. Добавить что-нибудь в заголовок запроса (например, basic auth)
Сначала сделаем requestClosure — это замыкание, в котором мы можем модифицировать отправляемый запрос:
Этот requestClosure надо обязательно добавить в provider:
2. Продебажить запрос
В Moya есть крутая штука — плагины, советую изучить их поподробней. Один из плагинов, например, автоматически выводит в консоль все ваши запросы:
Я предпочитаю BDD стиль тестов, поэтому для unit-тестирования будем использовать библиотеки Quick и Nimble. Добавим их в наш Podfile:
Теперь пишем небольшой тест:
Запускаем тесты, убеждаемся что они пройдены, после чего убеждаемся что сетевая часть покрыта тестами на 100% (как включить code coverage в xcode читайте здесь).
В этой статье я хотел дать читателям базовое представление о мощной сетевой библиотеке Moya, намеренно опустив нюансы для того, чтобы вы самостоятельно смогли исследовать её и насладиться развитым инструментарием, который позволяет решать широкий спектр задач при выстраивании сетевого слоя в iOS разработке. Исходный код ждёт Вас на Github.
Первое знакомство
На самом деле, на эту библиотеку я натыкался несколько раз и она даже пылилась у меня в закладках браузера, но откладывал её изучение, о чём впоследствии не раз пожалел. Авторы этой библиотеки выложили красноречивую картинку «до» и «после» в своём репозитории:

Впечатляет, правда? Суть библиотеки сводится к тому, что всю сетевую часть можно интегрировать быстро и минимальными телодвижениями — всю низкоуровневую работу за вас сделает Moya.
Начинаем интеграцию
Создадим Single-View Applicaton и и подключим библиотеку к нашему проекту (для маппинга я предпочитаю библиотеку ObjectMapper, для подключения сторонних зависимостей — CocoaPods)
Podfile
platform :ios, '9.0'
def app_pods
pod 'ObjectMapper', '~> 2.2'
pod 'Moya'
pod 'Moya-ObjectMapper'
end
target 'MoyaExample' do
use_frameworks!
app_pods
# Pods for MoyaExample
target 'MoyaExampleTests' do
inherit! :search_paths
app_pods
end
end
Далее нам нужно создать файл с запросами, делается это так:
import Moya enum MoyaExampleService { case getRestaurants(page: Int?, perPage: Int?) } extension MoyaExampleService: TargetType { var baseURL: URL { return URL(string: "http://moya-example.svyatoslav-reshetnikov.ru")! } var path: String { switch self { case .getRestaurants: return "/restaurants.json" } var method: Moya.Method { return .get } var parameters: [String: Any]? { return nil } var parameterEncoding: ParameterEncoding { return URLEncoding.default } var sampleData: Data { return Data() } var task: Task { return .request } }
В этом файле происходит настройка запросов. В самом начале мы видим enum — это наш будущий сервис со всеми запросами. Можно запихнуть все запросы в один сервис, но в больших проектах я рекомендую придерживаться буквы I из SOLID и не превращать файл в кашу. После перечисления всех запросов в enum нам нужно расширить класс протоколом
TargetType. Давайте рассмотрим подробней содержание этого протокола:1.
var baseURL — это адрес сервера, на котором лежит RESTful API.2.
var path — это роуты запросов.3.
var method — это метод, который мы хотим послать. Moya ничего не придумывает и берёт все методы из Alamofire.4.
var parameters — это параметры запроса. На данном этапе библиотеку не волнует будут ли эти параметры в теле запроса (POST) или в url (GET), эти нюансы определяются позже. Пока просто пишем параметры, которые мы хотим передать в запросе.5.
var parameterEncoding — это кодировка параметров, также берётся из Alamofire. Можно сделать их как json, можно как url, можно как property list.6.
var sampleData — это так называемые stubs, используются для тестирования. Можно взять стандартный ответ от сервера, сохранить его в проекте в формате JSON и затем использовать в unit тестах.7.
var task — это задача, которую мы будем выполнять. Их всего 3 — request, download и upload.Применяем в проекте
Для того, чтобы начать использовать Moya, нам необходимо создать Provider — это абстракция библиотеки, которая даёт доступ к запросам:
let provider = MoyaProvider<MoyaExampleService>()
После этого можно
provider.request(.getRestaurants()) { result in switch result { case .success(let response): let restaurantsResponse = try? response.mapObject(RestaurantsResponse.self) // Do something with restaurantsResponse case .failure(let error): print(error.errorDescription ?? "Unknown error") } }
Добавляем реактивности
Moya поддерживает ReactiveSwift и RxSwift. Лично я предпочитаю последнюю библиотеку, поэтому мой пример будет для неё. Для начала давайте добавим нужные зависимости:
Podfile
platform :ios, '9.0'
def app_pods
pod 'ObjectMapper', '~> 2.2'
pod 'Moya'
pod 'Moya-ObjectMapper'
pod 'Moya/RxSwift'
pod 'Moya-ObjectMapper/RxSwift'
end
target 'MoyaExample' do
use_frameworks!
app_pods
# Pods for MoyaExample
target 'MoyaExampleTests' do
inherit! :search_paths
app_pods
end
end
def app_pods
pod 'ObjectMapper', '~> 2.2'
pod 'Moya'
pod 'Moya-ObjectMapper'
pod 'Moya/RxSwift'
pod 'Moya-ObjectMapper/RxSwift'
end
target 'MoyaExample' do
use_frameworks!
app_pods
# Pods for MoyaExample
target 'MoyaExampleTests' do
inherit! :search_paths
app_pods
end
end
И наш код трансформируется следующим образом:
let provider = RxMoyaProvider<MoyaExampleService>() provider.request(.getRestaurants()) .mapObject(RestaurantsResponse.self) .catchError { error in // Do something with error return Observable.error(error) } .subscribe( onNext: { response in self.restaurants = response.data } ) .addDisposableTo(disposeBag)
Пара трюков с Moya
Обо всех возможностях Moya рассказывать долго, поэтому советую после прочтения этой статьи заглянуть в документацию. А я сейчас покажу несколько вещей, которые могут пригодиться и Вам:
1. Добавить что-нибудь в заголовок запроса (например, basic auth)
Сначала сделаем requestClosure — это замыкание, в котором мы можем модифицировать отправляемый запрос:
let requestClosure = { (endpoint: Endpoint<MoyaExampleService>, done: MoyaProvider.RequestResultClosure) in var request = endpoint.urlRequest request?.setValue("set_your_token", forHTTPHeaderField: "XAuthToken") done(.success(request!)) }
Этот requestClosure надо обязательно добавить в provider:
let provider = RxMoyaProvider<MoyaExampleService>(requestClosure: requestClosure)
2. Продебажить запрос
В Moya есть крутая штука — плагины, советую изучить их поподробней. Один из плагинов, например, автоматически выводит в консоль все ваши запросы:
let provider = RxMoyaProvider<MoyaExampleService>(plugins: [NetworkLoggerPlugin(verbose: true)])
Unit тесты
Я предпочитаю BDD стиль тестов, поэтому для unit-тестирования будем использовать библиотеки Quick и Nimble. Добавим их в наш Podfile:
Podfile
platform :ios, '9.0'
def app_pods
pod 'ObjectMapper', '~> 2.2'
pod 'Moya'
pod 'Moya-ObjectMapper'
pod 'Moya/RxSwift'
pod 'Moya-ObjectMapper/RxSwift'
end
def test_pods
pod 'Quick'
pod 'Nimble'
end
target 'MoyaExample' do
use_frameworks!
app_pods
# Pods for MoyaExample
target 'MoyaExampleTests' do
inherit! :search_paths
app_pods
test_pods
end
end
def app_pods
pod 'ObjectMapper', '~> 2.2'
pod 'Moya'
pod 'Moya-ObjectMapper'
pod 'Moya/RxSwift'
pod 'Moya-ObjectMapper/RxSwift'
end
def test_pods
pod 'Quick'
pod 'Nimble'
end
target 'MoyaExample' do
use_frameworks!
app_pods
# Pods for MoyaExample
target 'MoyaExampleTests' do
inherit! :search_paths
app_pods
test_pods
end
end
Теперь пишем небольшой тест:
import Quick import Nimble import RxSwift import Moya @testable import MoyaExample class NetworkTests: QuickSpec { override func spec() { var testProvider: RxMoyaProvider<MoyaExampleService>! let disposeBag = DisposeBag() beforeSuite { testProvider = RxMoyaProvider<MoyaExampleService>(stubClosure: MoyaProvider.immediatelyStub) } describe("testProvider") { it("should be not nil") { expect(testProvider).toNot(beNil()) } } describe("getRestaurants") { it("should return not nil RestaurantsResponse object") { testProvider.request(.getRestaurants()) .mapObject(RestaurantsResponse.self) .subscribe( onNext: { response in expect(response).toNot(beNil()) } ) .addDisposableTo(disposeBag) } } } }
Запускаем тесты, убеждаемся что они пройдены, после чего убеждаемся что сетевая часть покрыта тестами на 100% (как включить code coverage в xcode читайте здесь).
Заключение
В этой статье я хотел дать читателям базовое представление о мощной сетевой библиотеке Moya, намеренно опустив нюансы для того, чтобы вы самостоятельно смогли исследовать её и насладиться развитым инструментарием, который позволяет решать широкий спектр задач при выстраивании сетевого слоя в iOS разработке. Исходный код ждёт Вас на Github.
