はじめに
ReactiveSwiftでAlamofireを使う場合のAPIクライアントのサンプル実装
JSONのdecodeやエラーの構造体などについては記事には書かない
実装
Service.swift
import Foundation
import Alamofire
import Result
import ReactiveSwift
import Argo
internal enum Router: URLRequestConvertible {
static let baseURL = "https://example.com"
case sample(sampleId: Int)
var requestProperties: (method: Alamofire.HTTPMethod, path: String, query: [String: Any]) {
switch self {
case let .sample(sampleId):
let params: [String: Any] = ["sampleId": sampleId]
return (.get, "/v1", params)
}
}
func asURLRequest() throws -> URLRequest {
let url = URL(string: Router.baseURL)!
var urlRequest = URLRequest(url: url.appendingPathComponent(requestProperties.path))
urlRequest.httpMethod = requestProperties.method.rawValue
return try Alamofire.URLEncoding.default.encode(urlRequest, with: requestProperties.query)
}
}
public struct Service {
// Service
internal func fetchSample(sampleId: Int)
-> SignalProducer<Sample, ErrorEnvelope> {
return request(.sample(sampleId: sampleId))
}
// Request
private func request<M: Argo.Decodable>(_ router: Router)
-> SignalProducer<M, ErrorEnvelope> where M == M.DecodedType {
return self.rac_JSONResponse(router)
.flatMap(.concat, decodeModel) // json decode
}
private func request<M: Argo.Decodable>(_ router: Router)
-> SignalProducer<[M], ErrorEnvelope> where M == M.DecodedType {
return self.rac_JSONResponse(router)
.flatMap(.concat, decodeModels) // json decode
}
// Session
private func rac_JSONResponse(_ router: Router) -> SignalProducer<Any, ErrorEnvelope> {
return self.rac_dataResponse(router)
.map(parseJSONData)
.flatMap(.concat) { json -> SignalProducer<Any, ErrorEnvelope> in
guard let json = json else {
return .init(error: .couldNotParseJSON)
}
return .init(value: json)
}
}
private func rac_dataResponse(_ router: Router) -> SignalProducer<Data, ErrorEnvelope> {
let dataRequest = Alamofire.request(router)
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
let producer = self.dataResponse(with: dataRequest)
print("API Starting request \(dataRequest.request?.url?.absoluteString ?? "nil")")
return producer
.flatMap(.concat) { response -> SignalProducer<Data, ErrorEnvelope> in
guard let data = response.data else {
return SignalProducer(error: .couldNotParseErrorEnvelopeJSON)
}
if response.error != nil {
if let json = parseJSONData(data) {
switch decode(json) as Decoded<ErrorEnvelope> {
case let .success(envelope):
print("API Failure \(dataRequest.request?.url?.absoluteString ?? "nil") \n Error - \(envelope)")
return SignalProducer(error: envelope)
case let .failure(error):
print("API Failure \(error) \n Argo decoding error - \(error)")
return SignalProducer(error: .couldNotDecodeJSON(error))
}
} else {
print("API Failure \(dataRequest.request?.url?.absoluteString ?? "nil")")
return SignalProducer(error: .couldNotParseErrorEnvelopeJSON)
}
}
print("API Success \(dataRequest.request?.url?.absoluteString ?? "nil")")
return SignalProducer(value: data)
}
}
private func dataResponse(with request: DataRequest, queue: DispatchQueue? = nil) ->
SignalProducer<DefaultDataResponse, NoError> {
return SignalProducer { observer, disposable in
let response = request.response(queue: queue) { defaultDataResponse in
observer.send(value: defaultDataResponse)
observer.sendCompleted()
}
disposable.observeEnded {
response.cancel()
}
if (Alamofire.SessionManager.default.startRequestsImmediately == false) {
request.resume()
}
}
}
private func parseJSONData(_ data: Data) -> Any? {
return (try? JSONSerialization.jsonObject(with: data, options: []))
}
}