Alamofire + SwiftTask + ObjectMapperでAPIKitのようなAPIClientを実装する

# はじめに

swiftで優れたAPIクライアントの1つに[APIKit](https://github.com/ishkawa/APIKit)があります。
APIKitはタイプセーフでswiftライクな非常に扱いやすいライブラリです。

今回、開発しているアプリで通信周りの設計をいろいろと考えていたところAPIKitに出会い、感銘を受けました。
ざっくりと使用感を下記にまとめてみました。(調査不足な点があるかもしれません。)

__APIKitの優れている点__

* タイプセーフであること
* リクエストの戻り値がパース済みのモデルとして渡せること
* `RequestType`プロトコルに準拠することでAPIの動作が確定できること(場合によるが、独自実装やプロパティを保持せず、API情報だけ定義されてるため、それを見るだけでAPIの仕様がある程度把握できる) <- `ここが一番好きなポイント`
* ジェネリクスにより、リクエストのインターフェースが1つに収まってること

__APIKitのいまひとつな点__

* 通信内容のデバッグが難しい(リクエストヘッダーなどをprintしたくても、`Session`が通信を握っているのでブラックボックス化している。NSURLSessionでHookすればできるかも)
* 外部ライブラリという都合上、拡張に多少抵抗を感じてしまう(extensionを使えばカバーはできるが、個人的感覚として)
* 画像などのJSONフォーマット以外の用途だとお手軽感は薄いかも(そういったのでは通信ライブラリ単体でカバーしたい)

かゆいところにはあらかた手が届いてしまうAPIKitですが、アプリやAPIの仕様によっては辛い状況に落ちいってしまうことがありました。

そこで、今回はAPIKitのインターフェースを尊重しつつ、Alamofire, SwiftTask, ObjectMapperを組み合わせてオレオレAPIクライアントを実装したいと思います。

* [Alamofire](https://github.com/Alamofire/Alamofire) - 通信ライブラリ
* [SwiftTask](https://github.com/ReactKit/SwiftTask) - Promiseライブラリ
* [ObjectMapper](https://github.com/Hearst-DD/ObjectMapper) - ORマッパー

# 準備

## 環境設定

* XCode7.2 (swift 2.1)
* CocoaPods (1.0.0.beta.1)

## ライブラリ(CocoaPods)

```
platform :ios, '8.0'
use_frameworks!
 
target 'PMApiClient' do
  pod 'ObjectMapper', '~> 1.1.5'
  pod 'ReactKit', '~> 0.12.0'
  pod 'Alamofire', '~> 3.2.1'
end
```

今回はswift2.1での開発のため、各種ライブラリはswift2.1に対応したバージョンを使用します。

## サンプルAPI

サンプルAPIとしてQiitaの[itemsAPI](https://qiita.com/api/v2/docs#get-apiv2items)を使ってみます。
itemsAPIは記事の一覧を新しい順で返してくれるAPIです。レスポンスは下記。

```
[
  {
    "rendered_body": "<h1>Example</h1>",
    "body": "# Example",
    "coediting": false,
    "created_at": "2000-01-01T00:00:00+00:00",
    "id": "4bd431809afb1bb99e4f",
    "private": false,
    "tags": [
      {
        "name": "Ruby",
        "versions": [
          "0.0.1"
        ]
      }
    ],
    "title": "Example title",
    "updated_at": "2000-01-01T00:00:00+00:00",
    "url": "https://qiita.com/yaotti/items/4bd431809afb1bb99e4f",
    "user": {
      "description": "Hello, world.",
      "facebook_id": "yaotti",
      "followees_count": 100,
      "followers_count": 200,
      "github_login_name": "yaotti",
      "id": "yaotti",
      "items_count": 300,
      "linkedin_id": "yaotti",
      "location": "Tokyo, Japan",
      "name": "Hiroshige Umino",
      "organization": "Increments Inc",
      "permanent_id": 1,
      "profile_image_url": "https://si0.twimg.com/profile_images/2309761038/1ijg13pfs0dg84sk2y0h_normal.jpeg",
      "twitter_screen_name": "yaotti",
      "website_url": "http://yaotti.hatenablog.com"
    }
  }
]
```

# 実装

## APIクライアント

`Alamofire`と`SwiftTask`を組み合わせたAPIクライアントを実装します。
`Alamofire`は通信のprogressが取れたり、debugDescriptionで通信内容をログに出せたりするので便利です。

```swift:APIClient.swift
import Alamofire
import SwiftTask

public enum APIError : ErrorType {
    case ConnectionError(NSError)
    case InvalidResponse(AnyObject?)
    case ParseError(AnyObject?)
}

struct APIClient {
    static func request<T : Request>(request : T) -> Task<Float, T.Response, APIError> {

        let endPoint    = request.baseURL.absoluteString+request.path
        let params      = request.parameters
        let headers     = request.HTTPHeaderFields
        let method      = Alamofire.Method(rawValue: request.method.rawValue) ?? .GET
        let encoding    = (method == .GET) ? ParameterEncoding.URL : ParameterEncoding.JSON

        let task = Task<Float, T.Response, APIError> { progress, fulfill, reject, configure in
            let req = Alamofire.request(method, endPoint, parameters: params, encoding: encoding, headers: headers)
                .validate(statusCode: 200..<300)
                .progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
                    if totalBytesExpectedToWrite != 0 {
                        let prog = Float(totalBytesWritten / totalBytesExpectedToWrite)
                        progress(prog)
                    }
                }
                .responseJSON(completionHandler: { response in
                    if let error = response.result.error {
                        reject(.ConnectionError(error))
                        return
                    }

                    if let object = response.result.value, URLResponse = response.response {
                        guard let model = request.responseFromObject(object, URLResponse: URLResponse) else {
                            reject(.ParseError(object))
                            return
                        }
                        fulfill(model)
                    }else {
                        reject(.InvalidResponse(nil))
                    }
                })

            print(req)
            print(req.debugDescription)
        }
        return task
    }
}
```

## Request

APIKitの`RequestType`のようなプロトコルを作成します。`Request`と命名しました。
ここはリクエストのベースとなる部分で、それぞれのAPIリクエストの定義はこの`Request`プロトコルを継承して実装していきます。

処理が共通している部分はextensionに記述しておくと実装がスッキリします。
特殊な処理が必要になった場合はそれぞれのクラスで上書きしてしまえば良いです。

```swift:Request.swift
import ObjectMapper

public enum Method: String {
    case OPTIONS, GET, HEAD, POST, PUT, PATCH, DELETE, TRACE, CONNECT
}

protocol Request {
    typealias Response

    var baseURL : NSURL { get }
    var method : Method { get }
    var path : String { get }
    var parameters : [String : AnyObject] { get }
    var HTTPHeaderFields : [String : String] { get }

    func responseFromObject(object: AnyObject, URLResponse: NSHTTPURLResponse) -> Response?
    func errorFromObject(object: AnyObject, URLResponse: NSHTTPURLResponse) -> ErrorType?
}

extension Request {
    var baseURL : NSURL {
        return NSURL(string: "http://qiita.com/api/v2")!
    }

    var HTTPHeaderFields : [String : String] {
        return [:]
    }

    func errorFromObject(object: AnyObject, URLResponse: NSHTTPURLResponse) -> ErrorType? {
        return nil
    }
}

extension Request where Response:Mappable {
    func responseFromObject(object: AnyObject, URLResponse: NSHTTPURLResponse) -> Response? {
        print(self.decode(object.description))

        guard let model = Mapper<Response>().map(object) else{
            return nil
        }
        return model
    }
}
```

次にitemsAPIの定義をしていきます。
structにしておくとイニシャライザーが自動生成されるので便利です。

このAPIはレスポンスがArrayなので`responseFromObject`を上書きします。

```swift:ItemsRequest.swift
import UIKit
import ObjectMapper

struct ItemsRequest : Request {
    //MARK: Params
    var page : Int = 1
    var perPage : Int = 10

    //MARK: Protocol
    typealias ResponseComponent = QiitaItem
    typealias Response = [ResponseComponent]

    var method : Method {
        return .GET
    }

    var path : String {
        return "/items"
    }

    var parameters : [String : AnyObject] {
        return [
            "page"      : self.page,
            "per_page"  : self.perPage
        ]
    }

    func responseFromObject(object: AnyObject, URLResponse: NSHTTPURLResponse) -> Response? {
        guard let model = Mapper<ResponseComponent>().mapArray(object) else{
            return nil
        }
        return model
    }
}
```

## Model

itemのモデルを作成します。
パラメータは適当に見繕って追加しています。

```swift:QiitaItem.swift
import ObjectMapper

struct QiitaItem : Mappable {
    var id  : String = ""
    var title : String = ""
    var url : String = ""
    var user : QiitaUser?
    var createdAt : String = ""
    var updatedAt : String = ""

    init?(_ map: Map) { }

    mutating func mapping(map: Map) {
        id          <- map["id"]
        title       <- map["title"]
        url         <- map["url"]
        user        <- map["user"]
        createdAt   <- map["created_at"]
        updatedAt   <- map["updated_at"]
    }
}

struct QiitaUser: Mappable {
    var id : String = ""
    var name : String = ""
    var organization : String = ""
    var profileImageUrl : String = ""
    var description : String = ""
    var followeesCount : Int = 0
    var followersCount : Int = 0

    init?(_ map: Map) { }

    mutating func mapping(map: Map) {
        id              <- map["id"]
        name            <- map["name"]
        organization    <- map["organization"]
        profileImageUrl <- map["profile_image_url"]
        description     <- map["description"]
        followeesCount  <- map["followees_count"]
        followersCount  <- map["followers_count"]
    }
}
```

# 動作

`Request`プロトコルにに準拠したオブジェクトをrequest内に入れると、resultは`Request.Response`となります。
この場合、resultの型は`ItemsRequest`で定義した`[QiitaItem]`となります。

```swift:ViewController.swift
//MARK: - Private method
private func requestApi() {
	let request = ItemsRequest(page: 1, perPage: 10)
    APIClient.request(request).success{ result in
    		print(result)
       		self.items = result
       		self.tableView.reloadData()
    	}.failure{ error in
       		print(error)
    	}
    }
}
```

successやfailureなどそれぞれの戻り値は`Task`クラスなので、メソッドチェーンで処理を続けて書くことができます。
クロージャー地獄にならない点が`SwiftTask`の良い点です。
下記のように続けて書くことができます。

```swift:
APIClient.request(request).progress{ progress in
    print(progress)
}.success{ result in
    print(result)
    self.items = result
    self.tableView.reloadData()
}.failure{ error in
    print(error)
}.then { _ in
    print("done")
}
```

![screen1.png](https://qiita-image-store.s3.amazonaws.com/0/31823/728470be-b723-03e9-fd3e-dca0cdd4a017.png "screen1.png")

記事の一覧が取得できました!

# 終わりに

`APIKit`は`RequestType`プロトコルに準拠したAPI定義をボコボコ追加してくだけでAPI周りの実装ができ、かつ非常にスッキリとしたコードが書けるため、とても優れた設計だと思っています。
今回はそれを参考にしてオレオレAPIクライアントを実装してみました。

アプリのビジネスロジックや特殊なAPI仕様、クライアントの無茶振りな独自仕様などによって、修正や拡張を強いられるパターンがありますが、この場合`Request`部分を拡張することで簡単に機能追加が可能になります。
`APIClient`部分をすこしいじればキャッシュ機構も手軽に組み込めます。

ソースコードは下記githubにあります。
[PMApiClient - github](https://github.com/peromasamune/PMApiClient) 

# 参考資料

* [APIKitでSwiftらしいAPIクライアントを実装する - ishikawa blog](http://blog.ishkawa.org/2015/02/22/1424603797/)
* [[Swift] タイプセーフな軽量HTTPクライアント「APIKit」を試してみた - Developers.IO](http://dev.classmethod.jp/smartphone/apikit/)
* [SwiftでAPIクライアントをつくる (Alamofire+ObjectMapper) - Qiita](http://qiita.com/ekushida/items/c8cf74fb390bc58388cf)
* [RxSwift, ObjectMapper, APIKit で作る使いやすい API クライアント - Qiita](http://qiita.com/pm11/items/57b2dff4b1ac19bd89ba)