[RxSwift] HTTP通信部分にRxSwiftを使ってみる

2017.08.04

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Rxは便利そうだけど...なんだか難しそう

こんにちは。モバイルアプリサービス部の加藤です。皆さんはRxSwiftを使って開発していますか?
今回の記事は「Rxは便利そうだけど...なんだか難しそう」と感じている人にHTTP通信部分にRxSwiftを使ってみることで「あ、そんなに難しくないかも」と感じてもらえればと思います。
かく言う私も最近RxSwiftを触り出してまだまだ勉強中の身です。
(実は過去にC#をやっていたこともあり、Rx自体は知っていて「LINQみたいで便利」と思っていたのですが、C#にはasync/awaitあるし必要性をあまり感じてなかったのです)

Rxはもともと.NETから始まりました。Rxの歴史について興味のある人はこちらが詳しいのでご覧ください。
なお、RxSwiftのインストール方法はREADMEをご覧ください。

検証環境

本記事は以下の環境で検証を行っています。

  • macOS Sierra バージョン 10.12.6
  • Xcode Version 8.3.3 (8E3004b)
  • RxSwift 3.6.0
  • ObjectMapper 2.2.8

Rxの基本のキ

  • Observable(監視可能なもの)をSubscribe(イベントの購読)すると処理結果(イベント)が通知される
  • イベントの購読を解除する時はSubscribeした時の戻り値であるDisposableオブジェクトを使って解除する

スケジューラーとかオペレーターとか便利な機能はたくさんあるのですが、キホンはたったこれだけです。

HTTP通信部分にRxSwiftを使ってみる

Observable(監視可能なもの)を作る

まずは以下のコードをご覧ください。HTTPリクエストにはAlamofire、JSONのパースにはObjectMapperを使用しています。

func createSingle<T: Mappable>(withEndpoint endpoint: String,
                  method: HTTPMethod = .get,
                  parameters: Parameters? = nil,
                  encoding: ParameterEncoding = JSONEncoding.default) -> Single<T> {
    return Single<T>.create { singleEvent in
        let request = Alamofire.request(endpoint,
                                        method: method,
                                        parameters: parameters,
                                        encoding: encoding)
            .responseJSON { response in
                switch response.result {
                case .success(let data):
                    let entity = Mapper<T>().map(JSONObject: data)!
                    singleEvent(.success(entity))
                case .failure(let error):
                    singleEvent(.error(error))
                }
        }
        return Disposables.create { request.cancel() }
    }
}

SingleはSuccessかErrorのどちらかが1回だけ通知されるObservableだと思ってください。(実際は内部にObservableを保持しているラッパーです)

Alamofireのリクエスト結果によって成功もしくは失敗のイベントを発生させています。成功した時はパース結果であるモデルオブジェクトを、失敗した時はエラーのオブジェクトをイベントのパラメーターに渡しています。

Disposables.createの部分でイベントの購読を解除するためのDisposableを生成しています。ブロックにイベントの購読が解除される時に呼ばれる処理を指定できるのですが、ここではHTTPリクエストのキャンセル処理を行っています。

Subscribe(イベントの購読)する

次はSubscribeする側のコードです。

struct HogeEntity: Mappable {
    init?(map: Map) {
    }
    mutating func mapping(map: Map) {
        // ここでパースする
    }
}

func getHoge() -> Single<HogeEntity> {
    return createSingle(withEndpoint: "<APIエンドポイント>")
}

class HogePresenter {

    private let disposeBag = DisposeBag()

    func callAPI() {
        getHoge()
            .subscribe(
                onSuccess: { [unowned self] entity in
                    // 成功時に呼ばれるので処理をする。(例えば取得したモデルをViewに渡すなど)
                },
                onError: { [unowned self] error in
                    // 失敗時に呼ばれるので処理をする。(例えばエラーをViewに通知するなど)
                }
            ).disposed(by: disposeBag)
    }
}

getHogeを呼ぶとSingle<HogeEntity>が返るのでsubscribeしてonSuccessで成功時、onErrorで失敗時の処理を行います。

disposed(by: disposeBag)の部分でDisposeBagを使ってイベントの購読を解除しています。
DisposeBagは何かというと、「追加されたDisposableを自身が解放される時にまとめてdisposeしてくれる」という代物です。
DisposeBagをHogePresengerのインスタンス変数として定義しているので、解放されるタイミングはHogePresengerが解放されるタイミングです。つまり、Subscribeするオブジェクトが解放されるタイミングでイベントの購読をまとめて解除してくれます。そして前述の通り、そのタイミングでHTTPのリクエストもキャンセルされます。

Rx、難しくないでしょ?

今回はRxSwiftをHTTP通信部分に使ってみました。処理の成功/失敗を表現するだけであれば別にRxSwiftを使わずResult型を使えばいいのでは?と思うかもしれません。それはその通りだと思います。
Rxが真価を発揮するのはイベントの加工や合成です。このあたりは別途記事にできればと思いますが、この記事を通じてRxSwiftを使うこと自体は別に難しいことではないと感じてもらえれば幸いです。

参考記事