読者です 読者をやめる 読者になる 読者になる

RxSwiftとAPIKitの組み合わせに対する違和感

プログラミング swift iOS

みなさんさようなら.

最近ようやくRxSwiftを触り始めています.

RxSwiftでAPI通信を含むような動作をさせたいとき,何を使おうかと迷ったので,その話をします.

swiftで書けるってことは,別に NSURLSession をそのまま使って rx_response を呼んでもいいわけです. ただ,そうはいっても世の中にはいくつかAPI呼び出し用のライブラリがあります.

Alamofireあたりはかなり有名ですよね. 前にアプリを作った時は,まだAlamofireが出てきていなくて,AFNetworkingを使っていました.

で,最近ではAPIKitというのがちょいちょい名前聞きますね. 名前聞くっていうか,高専時代の友人が作ってるんで,最初から知ってるんですけど,そういうつながりなしにも名前を聞くようになってきました.

RxSwift と APIKit

別に,RxSwiftを使わなくても,APIKitは使いやすいライブラリだとは思うんですが,RxSwiftと絡めて使おうという記事はいくつか見つかります.

qiita.com

RxSwift x APIKit

https://gist.github.com/hoppenichu/638e16b12ba080588cc2

qiita.com

実際にこれは試してみました.

で,言いたい.

RxSwiftとAPIKit,いうほど組み合わせたいもの?

APIKitの使いやすいところ

APIKitでSwiftらしいAPIクライアントを実装する

ここで本人も言ってるんですが,

  • レスポンスはモデルオブジェクトとして受け取れる
  • 成功時にレスポンスを非オプショナルな値で受け取れる
  • 失敗時にエラーを非オプショナルな値で受け取れる

あと,個人的に良い抽象化だなぁーと思うのは,protocol extensionでデフォルトの挙動を定義しつつ,各エンドポイントごとにclassなりstructなりで分離できる点ですかね. このくらいの軽さのものを,親クラスからの継承ではなくprotocol extensionで記述できるのは非常に便利に感じました.

RxSwiftで作るという意味

では翻って,RxSwiftはどうでしょう. ReactiveXの基本思想には深く踏み込むつもりはなくて,RxSwiftが万能だとも思っていません. ただ,RxSwiftのレールに乗って考えていくと, delegateやclosureは排除されていく のではないでしょうか.

qiita.com

ここで行われるようなObserverを使った記述によるメリットは,データの流れのわかりやすさではないでしょうか. 自作でdelegateを作ったりすると,処理を記述する場所は適切な場所になりますが,どのようなデータが渡ってくるのか,そしてどのタイミングでdelegateメソッドが呼ばれるのかが非常にわかりにくく,また,定義場所が離れているので特定するのが面倒です.

こういうものの苦労を軽減してくれる.

そしてもう一点,やはり同期処理と非同期処理の記述が楽という点.

RxSwiftとAPIKitの組み合わせ

どちらも便利なことはわかっています. では,組み合わせるとどういう実装をするのか? これは冒頭にサンプルも乗っていましたが,順番にいきます.

  1. Rx用のメソッドをAPIKitに生やす
extension Session {
    func rx_sendRequest<T: RequestType>(request: T) -> Observable<T.Response> {
        return Observable.create { observer in
            let task = self.sendRequest(request) { result in
                switch result {
                case .Success(let res):
                    observer.on(.Next(res))
                    observer.on(.Completed)
                case .Failure(let err):
                    observer.onError(err)
                }
            }
            return AnonymousDisposable { [weak task] in
                task?.cancel()
            }
        }
    }

    class func rx_sendRequest<T: RequestType>(request: T) -> Observable<T.Response> {
        return sharedSession.rx_sendRequest(request)
    }
}
  1. APIの定義を書く
protocol GitHubRequestType: RequestType {
}

extension GitHubRequestType {
    var baseURL: NSURL {
        return NSURL(string: "https://api.github.com")!
    }
}

struct FetchRepositoryRequest: GitHubRequestType {
    typealias Response = [GitHubRepository]

    var method: HTTPMethod {
        return .GET
    }

    var userName: String
    var path: String {
        return String(format: "/users/%@/repos", self.userName)
    }

    init(userName: String) {
        self.userName = userName
    }

    func responseFromObject(object: AnyObject, URLResponse: NSHTTPURLResponse) -> Response? {
        guard let dictionary = object as? [[String: AnyObject]] else {
            return nil
        }

        guard let repos: [GitHubRepository] = GitHubRepository.buildWithArray(dictionary) else {
            return nil
        }
        return repos
    }
}
  1. 実際にAPIを呼び出す
button.rx_tap
    .subscribeOn(backgroundScheduler)
    .flatMap {
        return Session.rx_response(request)
    }
    .observeOn(mainScheduler)
    .subscribeNext { [unowned self] response in
        let r = response[0]
        self.resultLabel.text = "name: \(r.fullName!)\nurl: \(r.url!)\nicon: \(r.ownerAvaterUrl!)\nupdated_at: \(r.updatedAt!)"
    }
    .addDisposableTo(disposeBag)

すみません,いろんなサンプルコピーしました.

で,違和感がいくつか.

Result型使わないよね?

Rxの実装でしか使わなくなってしまいます. 結局,レスポンスが二値で返ってきたとしても,Observerに乗せてしまえば,その後にResult型の恩恵に預かることはないのでは?

そうなると,開発者がResult型で嬉しい局面ってないのではないだろうか.

そう考えると,APIKitの持つ,型安全という特徴がいまいち活かせていないように見えます.

オブジェクト型へのマッピングがAPIKit内で行われる

通信自体はReactiveにしてしまって,Observerに包んでレスポンスを返してれたとしても,結局 responseFromObject を書いておかなきゃいけなくて,ここまで含めた結果がObserverに乗ってきますね.

そして,Observerとして流れてくるデータは,マッピングが終わった型が流れてくるわけです.

例えば,エンドポイントから401が返ってきた時には,当然オブジェクトへのマッピングをしたくないわけですが,その場合にも responseFromObject は呼び出されてしまいます. そしてオブジェクトにマッピングしようとして,エラーになるわけです. もっとも,この問題自体は responseFromObject 内でresponseのStatusCodeを見ることで解決できる話ではあるんですが,その処理を responseFromObject に書かなきゃいけないというのが,Observableを使っている恩恵を感じにくいところではあります.

また,これは好みの問題なのですが,せっかくRxで呼出し後のデータの流れを扱っているのに,パースの部分だけを別の,APIエンドポイントの定義クラスに実装しなければいけないのは,処理を記述する場所が分散してしまって見通しが悪いんじゃないかなぁーと思うわけです.

オプショナルの面倒さが変わったのが大きい

RxSwiftになって格段に扱いやすくなったと思うもののひとつに,オプショナルがあります. unwrapに失敗したとして,subscribe前であれば,それはthrowとして何かしらのエラーを流してしまえば,subscribe時に好きなように拾えるからです.

そのためか,APIKitの,非オプショナルで受け取れるというメリットは,RxSwift側がほとんど解消してしまったように思えました. かつては,それこそunwrapしてエラーのclosureを呼んだりしていて,本当に面倒なことばかりでしたが…….

ただ,まだそこまでRxSwiftもAPIKitも使い込んでいないので(というか何を使おうか悩んでみての感想なので),もしかしたら知らないところでもっと大きな利点があるんですかね?

余談

今回はAPIKitの話にしぼりましたが,RxSwiftの使いやすさは今回話題にしたこと以外にもいっぱいあるので使ってみてください.

個人的にはRxSwiftの使いやすさというのはやはりUI周りだと思っています. Observerはもちろん使いやすいのですが,Driverに感動しました. これはデータの変更をUI制御側に伝搬をさせたいときに非常に使いやすい.