Hai! Do you know the feeling of despondency when you need to integrate a project with another RESTful API? This is when once again you need to create some APIManager and fill it with Alamofire requests, and then associate them with data mapping models. Personally, I try to optimize all my work as much as possible, so I regularly study various libraries so as not to write a bunch of repetitive code and get rid of the routine. In one of these visits, I came across an excellent library of
Moya , which will be discussed.
First meeting
In fact, I stumbled upon this library several times and it even gathered dust in my browser bookmarks, but put it off to study it, which I regretted more than once. The authors of this library have posted an eloquent picture "before" and "after" in their repository:

Impressive, right? The essence of the library is that all the network part can be integrated quickly and with minimal gestures -
Moya will do all the low-level work for you.
Getting Integration
Create a Single-View Applicaton and connect the library to our project (for mapping, I prefer the
ObjectMapper library, for connecting third-party dependencies -
CocoaPods )
')
Podfileplatform :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
Next we need to create a file with requests, this is done like this:
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 } }
This file is setting up queries. At the very beginning we see enum - this is our future service with all the requests. You can push all requests into one service, but in large projects I recommend sticking with the letter I from
SOLID and not turning the file into a mess. After listing all the requests in enum, we need to extend the class with the
TargetType
protocol. Let's take a closer look at the contents of this protocol:
1.
var baseURL
is the address of the server that hosts the RESTful API.
2.
var path
are query routes.
3.
var method
is the method we want to send. Moya does not invent anything and takes all the methods from Alamofire.
4.
var parameters
are request parameters. At this stage, the library does not care whether these parameters will be in the request body (POST) or in the url (GET), these nuances are determined later. For now, just write the parameters we want to pass in the request.
5.
var parameterEncoding
is a parameter encoding, also taken from Alamofire. You can make them as json, you can as url, you can as property list.
6.
var sampleData
is the so-called stubs, used for testing. You can take the standard response from the server, save it in a project in JSON format and then use it in unit tests.
7.
var task
is a task that we will perform. There are only 3 of them - request, download and upload.
We apply in the project
In order to start using Moya, we need to create a Provider - this is an abstraction of a library that gives access to requests:
let provider = MoyaProvider<MoyaExampleService>()
After that, you can
marry to make a request using the provider:
provider.request(.getRestaurants()) { result in switch result { case .success(let response): let restaurantsResponse = try? response.mapObject(RestaurantsResponse.self)
Add reactivity
Moya supports ReactiveSwift and RxSwift. Personally, I prefer the latest library, so my example will be for her. First, let's add the necessary dependencies:
Podfileplatform: 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
And our code is transformed as follows:
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)
A pair of tricks with Moya
About all the possibilities of Moya to talk for a long time, so I advise you, after reading this article, to look into the
documentation . And now I will show a few things that can be useful to you:
1. Add something to the request header (for example, basic auth)
First, we make the requestClosure - this is the closure in which we can modify the request to be sent:
let requestClosure = { (endpoint: Endpoint<MoyaExampleService>, done: MoyaProvider.RequestResultClosure) in var request = endpoint.urlRequest request?.setValue("set_your_token", forHTTPHeaderField: "XAuthToken") done(.success(request!)) }
This requestClosure must be added to the provider:
let provider = RxMoyaProvider<MoyaExampleService>(requestClosure: requestClosure)
2. Make a request
In Moya there is a cool thing - plugins, I advise you to study them in more detail. One of the plugins, for example, automatically displays all your requests to the console:
let provider = RxMoyaProvider<MoyaExampleService>(plugins: [NetworkLoggerPlugin(verbose: true)])
Unit tests
I prefer the BDD style of tests, so for unit testing we will use the
Quick and
Nimble libraries. Add them to our Podfile:
Podfileplatform: 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
Now we write a small test:
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) } } } }
We run the tests, make sure that they are passed, after which we are convinced that the network part is covered with tests for 100% (how to include code coverage in xcode read
here ).
Conclusion
In this article, I wanted to give readers a basic understanding of the powerful network library Moya, deliberately omitting the nuances so that you can explore it yourself and enjoy the advanced tools that allow you to solve a wide range of tasks when building a network layer in iOS development. Source code is waiting for you on
Github .