LoginSignup
2
0

More than 3 years have passed since last update.

ReactiveSwift+Alamofire サンプル

Last updated at Posted at 2018-04-17

はじめに

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: []))
    }
}
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0