資料

内容

Appleが出しているThe Swift Programming Languageに”Swift is a type safe language.”書かれているように、 SwiftをSwiftらしく書くには型を意識したコードを書くのが良いと思っています。

型を意識する前は以下のようなAPIクライアントを使ってきたのですが、

let parameters = [
    "q": "APIKit",
    "sort": "stars",
    "order": "asc",
]

GitHub.call(.GET, "/search/repositories", parameters) { JSON, error in
    if error != nil {
        println("error: \(error!.localizedDescription)”)
    } else {
        self.repositories = /* model object from JSON */
    }
}

このような実装はSwiftの特性を活かすチャンスをいくつか逃しています。 実際にViewController等から利用する際に

  • エンドポイントが列挙されていないので存在するかどうかわからない
  • パラメーターに何でも入れられるのでどのようなキー/値を入れればよいのかわからない
  • closureに2つのOptionalが入るので、型の上では4パターンの組み合わせに対応する必要がある

といった問題に直面すると思います。 特にはじめの2つの問題は、ViewControllerを書いているのにAPIのドキュメントを見に行く羽目になるので、 コーディングのリズムを乱す原因となりますし、typoなどの単純ミスの原因にもなります。 また、closureに2つのOptionalが入る問題は、”JSONがnilならばerrorが入る”という約束によって2パターンに絞ることは可能ですが、 そういった約束は型で実現するべきだと思います。

APIKitはこれらの問題を解決する仕組みを持ったライブラリです。以下のような形式で利用できます。

let request = GitHub.Endpoint.SearchRepositories(query: "APIKit",
    sort: .Stars,
    order: .Ascending)

GitHub.sendRequest(request) { response in
    switch response {
    case .Success(let box):
        self.repositories = box.unbox
        
    case .Failure(let box):
        println("error: \(box.unbox.localizedDescription)")
    }
}
  • すべてのリクエストはクラスとしてEndpoint内に列挙できる
  • リクエストがクラスなのでパラメーターはイニシャライザで制限できる
  • レスポンスはLlamaKit.Resultで返ってくるので、2パターンしか存在しない(成功時はモデルオブジェクト, 失敗時はNSError)。

成功時にモデルオブジェクトを返せる理由は、リクエストの定義にレスポンスのモデルオブジェクトまで紐付けて定義しているためです。 実際のインターフェースはジェネリクスを利用した

func sendRequest<T: Request>(request: T, handler: (Result<T.Response, NSError>) -> Void)

という形になっており、リクエストTに対応したモデルオブジェクトT.Responseを成功時に返します。

リクエストの定義は意外と簡単で、APIKitのRequestというプロトコルに適合すれば完了です。

public protocol Request {
    typealias Response: Any
    
    var URLRequest: NSURLRequest? { get }
    
    func responseFromObject(object: AnyObject) -> Response?
}

例えばGitHub APIの/search/repositoriesに対しては以下のように定義します。

// https://developer.github.com/v3/search/#search-repositories
class SearchRepositories: APIKit.Request {
    enum Sort: String {
        case Stars = "stars"
        case Forks = "forks"
        case Updated = "updated"
    }

    enum Order: String {
        case Ascending = "asc"
        case Descending = "desc"
    }

    typealias Response = [Repository]

    let query: String
    let sort: Sort
    let order: Order

    var URLRequest: NSURLRequest? {
        return GitHub.URLRequest(.GET, "/search/repositories", ["q": query, "sort": sort.rawValue, "order": order.rawValue])
    }

    init(query: String, sort: Sort = .Stars, order: Order = .Ascending) {
        self.query = query
        self.sort = sort
        self.order = order
    }

    func responseFromObject(object: AnyObject) -> Response? {
        var repositories = [Repository]()

        if let dictionaries = object["items"] as? [NSDictionary] {
            for dictionary in dictionaries {
                if let repository = Repository(dictionary: dictionary) {
                    repositories.append(repository)
                }
            }
        }

        return repositories
    }
}

この定義はAPIドキュメントをSwiftに翻訳したかのようになっており、非常に気持ちいいです。 しかも、定義はこの1箇所のみで済むので、変更にも強いんじゃないかなーと思っています。 ぜひ、使ってみてください。

https://github.com/ishkawa/APIKit

感想

potatotipsは発表時間が短いのでAPIKitの気持ち良さをあまり説明できなかったのですが、一部の方には伝わったようでほっとしました。 懇親会ではたくさんの開発者の方とお話ができて良かったです。また機会があったら参加します。