Хай! Вам знакомо то чувство уныния, когда нужно интегрировать проект с очередным 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.
Поделиться с друзьями
rule
Почитал очереднюю статью про Moya.
И так не вижу вообще плюсов особых, вообще не убедительно. Ну и реактивность тут еще и пру строк кода добавила. Зачем она? Потому-что модно?
Просто навязывает одно архитектурное решение.
svyat_reshetnikov
В русскоязычном сегменте мало информации про Moya. Вот тут этой библиотеки касаются вскользь, но информация достаточно скудная, поэтому я решил восполнить этот пробел.
Как говорится, не нравится — не ешьте. Я не евангелист этой библиотеки, моя задача — показать что такое решение есть и объяснить как с ним работать, использовать его или нет — дело Ваше.
Реактивность хорошо подходит для своих задач, например биндинг данных или избегание callback-hell при вызове двух и более сетевых запросов. Если Вам нравится completion, который вложен в completion, который вложен в completion — это Ваше право. Мне вот не нравится, поэтому и использую RxSwift.
Реактивность частенько готовят с MVVM, я даже писал про это статью, но в целом это самодостаточная библиотека, которую можно использовать с любой архитектурой. В своих проектах я придерживаюсь архитектуры VIPER и RxSwift туда очень хорошо вписывается, поэтому про навязывание архитектурного решения Вы погорячились.
rule
Грубо. Но я не нежный. Вообще то вы утверждаете первым предложением: «Moya — как перестать беспокоиться о сетевой части и начать жить». Потом не привели ни одного убедительного доказательства того, что беспокоится больше не нужно. А на мой вопрос «а как же ?» отвечаете: «Не нравится — не ешьте». Как по мне не последовательно, могу ответить в вашем духе: «Не нравится критика — не пишите публичные статьи».
Да кто спорит, но мы же рассматриваем конкрентный случай. В вашем примере никаких абсолютно плюсов нет, а читатель же должен перестать беспокоится почему-то.
По поводу навязанной архитектуры, я имел ввиду архитектуру построения Moya. Он жестко диктует условия построения API слоя, в этом его сила для начинающих разработчиков, насколько я понимаю. К общей архитектуре прилоежния это отношения не имеет.
svyat_reshetnikov
В мыслях не было грубить. Интернет, к сожалению, не передаёт эмоциональную составляющую слов. «Не нравится — не ешьте» это, скорее, безразличие, но никак не грубость.
но при этом Вы в упор не видите преимуществ этой библиотеки. Что ж, придётся мне это сделать за Вас.Меня искренне смущает, что у Вас в профиле написано
Для начала идём в репозиторий библиотеки Moya и смотрим что там написали авторы. Я напишу сразу перевод:
Из своего личного опыта могу сказать, что скорость интеграции сетевой части с помощью Moya лично в моей работе увеличилась в разы. И тесты стало писать гораздо приятней.
Что касается этого:
А в чём, собственно, заключается эта «жёсткость»? Вы можете свободно манипулировать роутами, параметрами, методами, задачами, модифицировать вход и выход запроса, создавать свои собственные stubs для тестирования, даже писать свои плагины и использовать уже готовые решения. Если Вы про то, что нужно создавать т.н. файл с запросами по определённым правилам, то позвольте спросить — а что в этом плохого? Как по мне, так архитектура выстроена очень здорово — ничего лишнего и все сделано очень удобно. Так что я искренне не понимаю почему Вы считаете что это минус, при том условии, что Вам выдаются все инструменты для манипуляций.
Продолжая тему RxSwift А Вы сами как думаете, если я создам ещё один запрос и захочу вызвать 2 запроса подряд, то как это будет выглядеть нереактивно и реактивно? Очевидно что в нереактивном случае будет callback-hell:
Теперь посмотрим как это будет реактивно:
Вам больше какой вариант нравится? Я предпочитаю реактивный вариант.
Вот это интересно:
Критика должна быть обоснованной. Если бы вы написали «Я вот тут попробовал Moya, сравнил с Alamofire и не получил абсолютно никакого выигрыша ни в скорости интеграции сетевой части, ни в скорости написания unit-тестов», то для меня это была бы интересная дискуссия. А по Вашим комментариям складывается такое чувство, что Вы пробежались по статье, не нашли того что искали — неопровержимых доказательств необходимости немедленного перехода на Moya — и принялись критиковать. Проблема в том, что статью я писал не для того, чтобы кому-то что-то доказать, а для того, чтобы показать что есть такой инструмент, который лично мне очень сильно помогает в работе.
Отвечу и на это:
Начнём с того, что я обыграл название книги Дейла Карнеги «Как перестать беспокоиться и начать жить». Просто потому что мне захотелось так назвать свою статью. Относитесь к этому попроще что ли, перестаньте беспокоиться — и жить сразу станет легче и приятней)
Удачи!
rule
Так это может вы просто плохо без Maya писали.
Цитировать и переводить документацию было лишния, я умею читать и ходить по ссылкам.
Все достопремичательности что вы привели — это бла, бла, бла, смотрте как надо. А я не вижу в упор что именно это улучшает.
Навязанное архитектурное решение — это жестко следовать предолженному протоколу и организации коду, который сваливает в кучу все запросы, а если у меня не монолитный сервис и всё таки нжуно поддержать зависимости между вызовами вся эта структура рушится.
Есть альтернативные подходы, типа NSOperation для каждого запроса с зависимостями ( и не реактивность не понадобится и лапши с колбэками не будет).
Или просто самому Enum написать с запросами в Alamofiore? Что решает Moya не понятно, то что. у них написано в примерах можно с такими-же усилиями и стандартными средствами сделать.
Мне оба варианта не нравятся.
Ну тогда ей место в личном бложике. На хабре вроде принято остаивать свою точку зрения и техническое решение.
Вы так много написали и ни одного доказательства, кроме двух ужасных связанных запросов. Яснее мне не стало, что же решает ваша любимая Moya.
svyat_reshetnikov
По существу, Moya — просто один из подходов к созданию сетевой части в приложении. Мне этот подход нравится и я его использую, все преимущества я уже расписал. Если Вам этого мало — увы.
Букву I из SOLID никто не отменял. Ничего не мешает Вам создать несколько сервисов, я сам против того чтобы все запросы сваливать в одну кучу. Совсем не вижу проблемы, которую Вы описали.
Вы можете привести пример кода? Если Вы утверждаете, что можно интегрировать сетевую часть так же быстро и просто, а также легко протестировать, и всё это с помощью стандартных средств — я буду только рад узнать что-то новое.
Давайте вы не будете указывать мне, где мне публиковать свои статьи. Я взрослый человек и сам в состоянии решить этот вопрос. То, что для меня звучит убедительно, для Вас звучит неубедительно — это нормальная ситуация. Всё, что я смог — я сказал, убеждать Вас в мои обязанности не входит — мне за это не платят.
rule
Это вы сами придумали.
Из ваших слов:
значит до этого вы писали код, в который требовал больше времени и тяжело тестировался. Я сделал вывод что он был хуже, иначе зачем было переходить на Moya? Где тут личности?
Вот это я прошу от вас — напишите преимущества, не в стиле «стало лучше, проще тестировать и быстрее» — это на рекламный проспект крема для кожи похоже (в смысле одна вода без конкретики).
Вы протестуете против того, чтоб я указывал вам что делать и при этом указываете что делать мне. Не кажется вам это не последовательным?
rule
Ну и давайте ваше заключение разберем:
В чем вы измеряете мощность библиотек?
Насколько я понял никакого инструментария она не предоставляет. Что вы имеете ввиду, API? Если так, то почему он развитый, как вы измерили/оценили его развитость?
Какой это широкий спекрт задач, я хотел услышать хоть об одной из них. Ну и слова выстраивание вроде нет в русском языке.
svyat_reshetnikov
Такое ощущение, что Вам просто нечем заняться и вы решили придраться ко всему, к чему только можно, включая речевые обороты.
А вот это вообще самый последний аргумент. Я старался спокойно относиться к Вашим ошибкам, но Вы сами напросились. Разберём Ваш первый комментарий:Ошибки: очереднюю(очередную), и так (в этом случае нужно писать слитно), нужна запятая после слова итак, пру (пару), потому-что (потому что).
На этом считаю нашу дискуссию законченной.
rule
а на остальные вопросы не хотите ответить? Только граматику будем обсуждать?