Moya+RxSwift+CodableでAPI通信


はじめに

AlamofireをラップしたMoyaというライブラリを使ってみました。
RxSwift拡張もあるとのことなので、
Moya+RxSwift+Codableということで備忘も兼ねて記事にしました。
Moya:https://github.com/Moya/Moya

準備

今回はCocoapodsを使いました。

pod 'Moya/RxSwift'

APIの定義

MoyaではTargetTypeプロトコルに準拠したenumでAPIを定義します。
今回は簡単なログインAPIな想定です。

Login.swift
enum Login {
    // パスごとにcaseを切り分ける
    case login(id: String, pass: String)
}

extension Login: TargetType {
    // ベースのURL
    var baseURL: URL {
        return URL(string: "https://hogehoge.com")!
    }

    // パス
    var path: String {
        switch self {
        case .login:
            return "/login"
        }
    }

    // HTTPメソッド
    var method: Moya.Method {
        switch self {
        case .login:
            return .post
        }
    }

    // スタブデータ
    var sampleData: Data {
        let path = Bundle.main.path(forResource: "login_stub", ofType: "json")!
        return FileHandle(forReadingAtPath: path)!.readDataToEndOfFile()
    }

    // リクエストパラメータ等
    var task: Task {
        switch self {
        case .login(let id, let pass):
            return .requestParameters(parameters: ["id" : id, "pass": pass], encoding: URLEncoding.default)
        }
    }

    // ヘッダー
    var headers: [String: String]? {
        return nil
    }
}

MoyaではStubとしてローカルのjsonをレスポンスとして返すことが出来ます。
これが特に便利に感じました。

デコード用にDecodableに準拠したstructを定義します。

struct LoginResponse: Decodable {
    let userId: String
}

カスタム通信クラスを定義

MoyaはAlamofireをラップしているため、タイムアウト等を設定する場合には、
SessionManagerを別途カスタムする必要があります。
そのため、通信クラスであるMoyaProviderを継承したCustomMoyaProviderクラスを定義します。

CustomMoyaProvider.swift
final class CustomMoyaProvider<T: TargetType>: MoyaProvider<T> {

    public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
                requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
                stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
                callbackQueue: DispatchQueue? = nil,
                plugins: [PluginType] = [],
                trackInflights: Bool = false) {

        let sessionManager: SessionManager = {
            let configuration = URLSessionConfiguration.default
            // タイムアウトを設定する
            configuration.timeoutIntervalForRequest = 30
            return SessionManager(configuration: configuration)
        }()

        super.init(endpointClosure: endpointClosure,
                   requestClosure: requestClosure,
                   stubClosure: stubClosure,
                   callbackQueue: callbackQueue,
                   manager: sessionManager,
                   plugins: plugins,
                   trackInflights: trackInflights)
    }

}

API通信呼び出し

MoyaProviderが破棄されてしまうとSubscribeも止まってしまうため、
プロパティとして定義します。
前述したStubを利用する設定をCustomMoyaProvider定義時に渡します。
neverStub...通常のAPI通信
immediatelyStub...Stubを返す
delayedStub(x)...x秒後にStubを返す

private let loginProvider = CustomMoyaProvider<Login>(stubClosure: MoyaProvider.delayedStub(2))
private let disposeBag = DisposeBag()

通信します。
レスポンスをmapでdecodeし、subscribeに流します。

loginProvider.rx
    .request(.login(id: "id", pass: "pass"))
    .map { (response) -> LoginResponse? in
         return try? JSONDecoder().decode(LoginResponse.self, from: response.data)
    }.subscribe(onSuccess: { (response) in
         if let unwrappedResponse = response {
             print(unwrappedResponse)
         } else {
             print("error")
         }
    }, onError: { (error) in
         print("error")
    })
    .disposed(by: disposeBag)

通信部もシンプルでよいです。

以上となります。
何かございましたら指摘してくださると大変喜びます。