LoginSignup
32
38

More than 5 years have passed since last update.

RxSwiftを用いたシンプルなAPIクライアント実装

Last updated at Posted at 2015-07-19

※ 以下の内容は、Xcode6.3.2 (Swift 1.2) を前提とした話となっております。なお私自身iOS/Swiftでの開発は記事執筆時点で1ヶ月ほどの経験しかないので何かお作法的にまずい部分や、間違っている部分がありましたらコメント等でご指摘いただけると幸いです。

RxSwift/RxCocoa,SwiftyJSONの導入

RxSwift/RxCocoaはともに、CocoaPodsを使うことにより簡単に導入することができると思います。詳しくはRxSwiftのGitHub Repositoryに載っていますのでそちらを参照してください。

また、今回は例としてQiitaのAPIクライアントを実装しますのでJSONを簡単に取り扱えるように、SwiftyJSONも導入します。

シンプルなAPIクライアント実装

さて今回ご紹介するのはRxSwiftのExamplesに載っているようなシンプルなAPIクライアントの一例です。

ここではQiitaの GET /api/v2/tags を叩くAPIクライアントの実装を提示します。まずはAPIのレスポンスを突っ込むデータ構造を定義します。Qiita APIのドキュメントを参考に以下のように定義できると思います。

class Tag {

    let followersCount: Int
    let iconUrl: String?
    let id: String
    let itemsCount: Int

    init(id: String, itemsCount: Int, followersCount: Int, iconUrl: String?) {
        self.id = id
        self.itemsCount = itemsCount
        self.followersCount = followersCount
        self.iconUrl = iconUrl
    }

}

extension Tag: Printable {
    var description: String {
        return "Tag{id:\(id),itemsCount:\(itemsCount),followersCount:\(followersCount),iconUrl:\(iconUrl)}"
    }
}

extension Tag: Equatable {}
func ==(lhs: Tag, rhs: Tag) -> Bool {
    return lhs.id == rhs.id
        && lhs.itemsCount == rhs.itemsCount
        && lhs.followersCount == rhs.followersCount
        && lhs.iconUrl == rhs.iconUrl
}

次にAPIクライアントの実装ですが、基本的にはGETリクエストを投げるとJSONのレスポンスが返ってくるのでそれをTagに変換してあげるという形になるかと思います。実際にアプリケーションに組み込むときはコード中で強制的に unwrap している箇所についてケアをする必要があるかと思いますが、概ね以下のようなコードになると思います。

import RxSwift
import RxCocoa
import SwiftyJSON

class QiitaApiClient {

    private let context = Context.sharedInstance
    private let apiUrl = "http://qiita.com/api/v2/"
    private let convertFailureError = NSError(domain: "failed to convert", code: -1, userInfo: nil)
    private let connectionError = { (statusCode: Int) in NSError(domain: "connection error: status code=\(statusCode)", code: -1, userInfo: nil) }

    func getTags() -> Observable<[Tag]> {
        let request = NSURLRequest(URL: NSURL(string: apiUrl + "tags")!)
        return context.urlSession.rx_response(request)
            >- mapOrDie {
                (data: NSData!, response: NSURLResponse!) -> RxResult<[Tag]> in
                if let res = response as? NSHTTPURLResponse {
                    if res.statusCode != 200 { return failure(self.connectionError(res.statusCode)) }
                    else { return success(self.json2Tags(JSON(data: data))) }
                } else {
                    return failure(self.convertFailureError)
                }
            }
            >- observeSingleOn(context.mainScheduler)
    }

    private func json2Tags(json: JSON) -> [Tag] {
        return json.array!
            .map(json2Tag)
    }

    private func json2Tag(json: JSON) -> Tag {
        return Tag(
            id: json["id"].string!,
            itemsCount: json["items_count"].number?.integerValue,
            followersCount: json["followers_count"].number?.integerValue,
            iconUrl: json["iconUrl"].string
        )
    }

}

ここで SchedulerNSURLSession などを引き渡すために以下のような Context クラスを利用しています。

import RxSwift

class Context {

    static let sharedInstance = Context()

    let urlSession = NSURLSession.sharedSession()
    let backgroundWorkScheduler: ImmediateScheduler
    let mainScheduler: SerialDispatchQueueScheduler

    private init() {
        let operationQueue = NSOperationQueue()
        operationQueue.maxConcurrentOperationCount = 2
        operationQueue.qualityOfService = NSQualityOfService.UserInitiated
        backgroundWorkScheduler = OperationQueueScheduler(operationQueue: operationQueue)
        mainScheduler = MainScheduler.sharedInstance
    }

}

さて、この実装では client クラスがリクエストを生成したり、ステータスコードを見て処理をエラーハンドリングしたり、NSData から Tag へと変換を行ったりと様々な責務を持ってしまっています。簡単なアプリケーションならばこのままでも十分かもしれませんが、理想的にはひとつのクラスは単一の責務を持つようにしたいところですね。

RequestResult などを別クラスに切り出したAPIクライアント実装も色々手元で試してはいるので、その内容については今後また記事を書く時間ができたら投稿させていただきたいと思っています。

RxSwift/RxCocoaを用いるとコールバックを渡すパターンではなくなるため、APIクライアント側にコールバックを受け取る引数を作らずにすみ、すっきりしますね。また、データの変形やフィルタリングも mapfilter などをはじめとした様々なコンビネーターを利用して自在に行うことができるため良いのではないかと、私自身は考えています。

32
38
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
32
38