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

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

# RxSwift/RxCocoa,SwiftyJSONの導入

RxSwift/RxCocoaはともに、CocoaPodsを使うことにより簡単に導入することができると思います。詳しくは[RxSwiftのGitHub Repository](https://github.com/kzaher/RxSwift#build--install--run)に載っていますのでそちらを参照してください。

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


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

さて今回ご紹介するのは[RxSwiftのExamples](https://github.com/kzaher/RxSwift/tree/master/RxExample/RxExample/Examples)に載っているようなシンプルなAPIクライアントの一例です。

ここではQiitaの `GET /api/v2/tags` を叩くAPIクライアントの実装を提示します。まずはAPIのレスポンスを突っ込むデータ構造を定義します。[Qiita APIのドキュメント](http://qiita.com/api/v2/docs#get-apiv2tags)を参考に以下のように定義できると思います。

```swift
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` している箇所についてケアをする必要があるかと思いますが、概ね以下のようなコードになると思います。

```swift
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
        )
    }
    
}
```

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

```swift
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` へと変換を行ったりと様々な責務を持ってしまっています。簡単なアプリケーションならばこのままでも十分かもしれませんが、理想的にはひとつのクラスは単一の責務を持つようにしたいところですね。

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

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