Swift で WebAPI を叩いてみる

アプリを作っていると WebAPI を叩く機会が多いと思いますが、今回は Swift で書いてみました。

## これはなに

- 便利ライブラリの紹介
- あえて NSURLSession を使って叩く
- テスト書く

## 便利ライブラリ
後で紹介する NSURLSession を使ってもよいのですがいろいろと骨が折れるのでライブラリを使いましょう。
GET, POST, PUT, DELETE に対応していれば大体の API が叩けると思います。

### Alamofire
https://github.com/Alamofire/Alamofire

みんな大好き Alamofire 。いろんなところで紹介されているので「またか」という人もいるのではないでしょうか。
高機能で美しくレスポンスを受け取れるのがいいですね。
個人的には

```
Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
         .response { (request, response, data, error) in
                     println(request)
                     println(response)
                     println(error)
                   }
```

と request, response をメソッドチェイン的に書けるのが美しいですね。

### Net
https://github.com/nghialv/Net

Alamofire 一強みたいな雰囲気がありますが、 Net というライブラリもあります。
ぼくはこっちを先に知ったのでよく使っています。素直に NSURLSession をラップしている、という感じで Objective-C 書いてたエンジニアは親しみやすいと思います（今となっては Alamofire の方がいいなあ…と思っている）。また、実装自体も追いやすいので拡張させたりできます。

```
let url = "http://www.puqiz.com/get_path"
net.GET(absoluteUrl: url, params: params, successHandler: { responseData in
        let result = responseData.json(error: nil)
        NSLog("result \(result)")
    }, failureHandler: { error in
        NSLog("Error")
    })
```

_コードはそれぞれの README から拝借しました。_

## ライブラリを導入してみる

- git-submodule
- cocoapods

### git-submodule で入れる
後述の cocoapods が正式に対応していない以上、git-submodule で入れるのが一般的なのかなあと思います。みなさんはどうですか。
よく README に書いてあるのが

> hoge.swift を直接 Project に入れてね

というようなことですね。 `git clone` して入れてもいいですが Swift はすぐ仕様が変わるし、どのライブラリもアクティブにコミットされているのが多いので `git submodule` で導入するのがいいと思います。
コマンドの使い方などは http://qiita.com/sotarok/items/0d525e568a6088f6f6bb このへんを読めばいいです。

#### 悪い点

- 管理すべきものが git-submodule と cocoapods で分散する

ただでさえ `pod update` するのもめんどくさいのに `git submodule update` を打たないといけなくなり大変めんどくさくなります。

### cocoapods で入れる
現在、cocoapods は Swift に対応していませんが、 swift branch に checkout すればなんとか使うことができます。
詳しくは Web で： http://swiftwala.com/cocoapods-is-ready-for-swift/

#### プロジェクトルートに Gemfile を作成
```text:Gemfile
source 'https://rubygems.org'

gem 'cocoapods', :git => 'https://github.com/CocoaPods/CocoaPods.git', :branch => 'swift'
gem 'cocoapods-core', :git => 'https://github.com/CocoaPods/Core.git'
gem 'xcodeproj',  :git => 'https://github.com/CocoaPods/Xcodeproj.git'
gem 'claide', :git => 'https://github.com/CocoaPods/CLAide.git'
```

#### bundle install する
```
$ bundle install
```

#### Podfile を編集
```text:Podfile
source 'https://github.com/CocoaPods/Specs.git'

platform :ios, '8.0'
pod 'SwiftyJSON', :git => "https://github.com/orta/SwiftyJSON", :branch => "podspec"
pod 'Alamofire', :git => "https://github.com/mrackwitz/Alamofire.git", :branch => "podspec"
```
この例だと SwiftyJSON と Alamofire を導入しています。

#### pod install する
```
$ bundle exec pod install
```
毎回 `bundle exec` が必要なようです。

## NSURLSession で叩いてみる
ライブラリを紹介したあとで蛇足感ありますが、Swift で書いてみます。
今回は [tiqav の API](http://dev.tiqav.com) を叩いてみました。

```swift:APIRequest.swift
import Foundation

class APIRequest {
    init() {
        // 初期化
    }
    
    class func getRandom() {
        let url = NSURL(string: "http://api.tiqav.com/search/random.json")
        let config = NSURLSessionConfiguration.defaultSessionConfiguration()
        let session = NSURLSession(configuration: config)
        
        let request = NSURLRequest(URL: url!)
        let task = session.dataTaskWithRequest(request, completionHandler: {
            (data, response, error) in
            switch (data, response, error) {
            case (_, _, .Some(error)):
                // エラー処理
                println(error.localizedDescription)
            case (.Some(data), .Some(response), _):
                if (response as NSHTTPURLResponse).statusCode == 200 {
                    // なんか処理
                    var error: NSError?
                    let jsonArray = NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments, error: &error) as NSArray
                    println(jsonArray)
                } else {
                    // 処理
                }
            default:
                break
            }
        })
        
        task.resume()
    }
}
```

Swift の良いところは tuple にあると思います。
request の結果が `(data, response, error)` というタプルで返ってくるのでエラー処理などを if 文書かずに switch で分けて書くことができます。
Swift の Optional には `.Some` と `.None` があるのでそれで nil かどうかを調べることができます。
それについては [この記事](http://qiita.com/koher/items/5dd6ce7427e7ecff4249) が詳しいです。
tuple で処理するところ以外は Objective-C と同じなので説明する必要はないでしょう。
_というかこの switch 文の場合分けの記事をどこかで見た気がしたんだが忘れてしまったのでご存知の方教えていただけると助かります。_

## テスト書く
API 叩くあたりのクラスはちゃんとテストを書きたいですね。
というと、

- レスポンスのモックを用意する
- HTTPRequest をフックしてモックを返すようにする

ということが必要になりそうですね。

### OHHTTPStubs
https://github.com/AliSoftware/OHHTTPStubs

そのようなライブラリはたくさんありますが、一応これを紹介しておきます。
ただ、Objective-C で書かれており、cocoapods で導入したところうまく動きませんでした。
_原因としては Objective-C のマクロを Swift がうまく処理できないのかなあというエラーがでていましたがイマイチ不明です。_

### aerogear-ios-httpstub
https://github.com/aerogear/aerogear-ios-httpstub

こちらは OHHTTPStubs の Swift 移植のものです。これはうまく動きました。

```
StubsManager.stubRequestsPassingTest({ (request: NSURLRequest!) -> Bool in
    return true
}, withStubResponse:( { (request: NSURLRequest!) -> StubResponse in
     return StubResponse(filename: "mystubbedjson.json", location:.Bundle(NSBundle(forClass: AGURLSessionStubsTests.self)), statusCode: 200, headers: ["Content-Type" : "text/json"])
}))
```
というようなコードを HTTP リクエストを送る前に呼んでおくとフックして、この例だと `mystubbedjson.json` を返してくれるというわけです。
これでいつでも同じレスポンスを得ることができ、API のテストが書けるというわけです。

## まとめ
- メソッドチェインで書くとかっこいい
- タプル使うと見やすく書ける
- cocoapods の Swift 対応を求む
- Swift 移植のライブラリも登場しはじめており非常に期待がもてる

長くなってしまいましたが終わりです。 Swift は見やすいコードが書けるのでとてもいいですね。