By default, Swift uses the URLSession class for communication processing, but Moya is a library that serves as a wrapper for it.
Since it can be written conveniently, it seems to be a standard method at the development site. Especially convenient when combined with RxSwift.
For the time being, it is better to know the URLSession class to some extent, so beginners should first get used to URLSession and then move to Moya and realize that it can be used conveniently.
Official API client made with Swift4 + Moya + RxSwift + Codable
APIClient.swift
APIClient.swift
final class APIClient {
private init() {}
static let shared = APIClient()
// MARK: - Private
private let provider = MoyaProvider<MultiTarget>()
private let stubProvider = MoyaProvider<MultiTarget>(stubClosure: MoyaProvider.immediatelyStub)
// MARK: - Public
func request<G: ApiTargetType>(_ request: G) -> Single<G.Reponse> {
Single<G.Reponse>.create { observer in
self.makeRequest(request)
.subscribe(onSuccess: { response in
observer(.success(response))
}, onError: { error in
//Error handling, etc. that you want to do in common throughout the project
observer(.error(error))
})
}
}
func makeRequest<G: ApiTargetType>(_ request: G) -> Single<G.Reponse> {
provider.rx
.request(MultiTarget(request))
.flatMap({ response -> Single<Response> in
//Checking the response header
return Single.just(try response.lookingAllHeaderFields())
})
.flatMap { response -> Single<Response> in
//Error code check
return Single.just(try response.successfulStatusCodesPolicy())
}
.map(G.Reponse.self, failsOnEmptyData: false)
}
func requestStub<G: ApiTargetType>(_ request: G) -> Single<G.Reponse> {
Single<G.Reponse>.create { observer in
self.makeRequestStub(request)
.subscribe(onSuccess: { response in
observer(.success(response))
}, onError: { error in
if let error = error as? MoyaError {
//Error handling, etc. that you want to do in common throughout the project
observer(.error(error))
})
}
}
func makeRequestStub<G: ApiTargetType>(_ request: G) -> Single<G.Reponse> {
stubProvider.rx
.request(MultiTarget(request))
.flatMap({ response -> Single<Response> in
//Checking the response header
return Single.just(try response.lookingAllHeaderFields())
})
.flatMap { response -> Single<Response> in
//Error code check
return Single.just(try response.successfulStatusCodesPolicy())
}
.map(G.Reponse.self, failsOnEmptyData: false)
}
}
ApiTargetType.swift
ApiTargetType.swift
import Foundation
import Moya
protocol ApiTargetType: TargetType {
associatedtype Response: Codable
}
extension ApiTargetType {
/// The target's base `URL`.
var baseURL: URL {
URL(string: "\(URLs.baseURL)")!
}
}
--Since API communication is performed, it is necessary to avoid the situation where the object of MoyaProvider class is released during communication. On the side that uses the API client, it is also possible to retain the object of the MoyaProvider class for a sufficiently long period (such as setting it as a member variable). Here, to avoid such trouble, set shared
as Singleton object so that MoyaProvider will survive throughout the execution of the program. ing.
APIClient.swift
final class APIClient {
private init() {}
static let shared = APIClient()
--- MultiTarget
is used so that different types of TargetType can be assigned to MoyaProvider. As a result, the API client user side can save the trouble of passing the TargetType to the MoyaProvider through generics, and can hide the existence of the MoyaProvider from the user side.
APIClient.swift
private let provider = MoyaProvider<MultiTarget>()
--If necessary, you can pass a Plugin when initializing MoyaProvider. This can be used if you want to describe any side effects, such as before sending a request or after receiving a response. As stated in the official comment, it is used for sending a request, logging when receiving a response, putting in and out an indicator (a thing that turns around indicating that it is loading), etc. It can be used when you want to change the URL Request setting before sending.
APIClient.swift
private let provider = MoyaProvider<MultiTarget>(plugins: [FooPlugin()])
Plugin.swift
/// A Moya Plugin receives callbacks to perform side effects wherever a request is sent or received.
///
/// for example, a plugin may be used to
/// - log network requests
/// - hide and show a network activity indicator
/// - inject additional information into a request
public protocol PluginType {
/// Called to modify a request before sending.
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest
/// Called immediately before a request is sent over the network (or stubbed).
func willSend(_ request: RequestType, target: TargetType)
/// Called after a response has been received, but before the MoyaProvider has invoked its completion handler.
func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType)
/// Called to modify a result before completion.
func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError>
}
--If you use stubProvider, you can return the sample data described in each TargetType as a response. As for the behavior of stubs, .immediatelyStub returns stub data immediately, but .delay can return data after the specified number of seconds, which is effective when you want to test in a situation closer to production.
APIClient.swift
private let stubbingProvider = MoyaProvider<MultiTarget>(stubClosure: MoyaProvider.immediatelyStub) //.delayed(seconds: 1.0)Etc. are also possible
--The returned response contains status code, header field, and data, but if the processing you want to perform for these is common to the entire project, it is better to describe it in the API client. Error handling, logging, etc.
APIClient.swift
// MARK: - Public
func request<G: ApiTargetType>(_ request: G) -> Single<G.Reponse> {
Single<G.Reponse>.create { observer in
self.makeRequest(request)
.subscribe(onSuccess: { response in
observer(.success(response))
}, onError: { error in
//Error handling, etc. that you want to do in common throughout the project
observer(.error(error))
})
}
}
func makeRequest<G: ApiTargetType>(_ request: G) -> Single<G.Reponse> {
provider.rx
.request(MultiTarget(request))
.flatMap({ response -> Single<Response> in
//Checking the response header
return Single.just(try response.lookingAllHeaderFields())
})
.flatMap { response -> Single<Response> in
//Error code check
return Single.just(try response.successfulStatusCodesPolicy())
}
.map(G.Reponse.self, failsOnEmptyData: false)
}
If there is something that is common to the entire project (typically, the base URL, etc.), it is a good idea to put it out and create a TargetType protocol.
python
protocol ApiTargetType: TargetType {
associatedtype Response: Codable
}
extension ApiTargetType {
/// The target's base `URL`.
var baseURL: URL {
URL(string: "\(URLs.baseURL)")!
}
}
python
func fetchData() -> Single<HogeEntity> {
return APIClient.shared.request(HogeTargetType())
}
HogeTargetType.swift
import Foundation
import Moya
struct HogeTargetType: ApiTargetType {
typealias Response = HogeEntity
var path: String {
return "/hogehoge/"
}
var method: Moya.Method {
return .get
}
var task: Task {
let param: [String: Any] = [
"id": id
]
return .requestParameters(parameters: param, encoding: URLEncoding.default)
}
var headers: [String: String]? {
[
"Content-Type": "application/json",
"x-auth-token": API.authToken
]
}
var sampleData: Data {
let path = Bundle.main.path(forResource: "Hoge", ofType: "json")!
guard let file = FileHandle(forReadingAtPath: path) else {
return Data()
}
return file.readDataToEndOfFile()
}
// MARK: - Arguments
/// ID
var id: String {
return DataManager.shared.id ?? ""
}
init() {}
}
HogeEntity.swift
struct HogeEntity: Codable {
let fuga: String
Hoge.json
{
"fuga": "test"
}
API client made with Swift4 + Moya + RxSwift + Codable Moya/Examples/Multi-Target/ViewController.swift