ことさら−古都プログラマーの更級日記

京都でお寺を回りながら御朱印集めをしていたり、LoLをしたり試合を見に行ったりしているエンジニアのブログです。技術的なはなしとか日常的なはなし、カメラやLoLや競馬の話も書きます。右メニューに検索やらカテゴリーやらがあるので、見たい記事だけ見てね!

【Swift4】URLSessionを利用してApiClientを実装してみるその1【URLSessionとstruct, Codable】

もくじ

f:id:yoshiki_utakata:20180708001344j:plain

はじめに

最近再びSwiftを触り始めました。何のアプリを作るにしてもAPIを叩くことは多いかと思いますし、最初にAPIを叩く部分を実装することも多いかと思います。そこで、URLSessionを利用してjsonAPIを叩くAPI Clientを実装してみようと思います。

環境

  • Swift 4

URLSessionの使い方

今回はとりあえず、tokenなどは不要でとにかく叩くだけでいいAPIを叩く。NASA Image APIを使う。

APIを叩くにはURLSessionを使うのが簡単。実装はこう。

let urlOpt = URL(string: "https://images-api.nasa.gov/search?media_type=image")
// URLは固定値なので!できる前提で進めて問題ない
let request = URLRequest(url: urlOpt!)
let task = URLSession.shared.dataTask(with: request) { (data, urlResponse, error) in
  // レスポンスに対する処理をここで行う(コールバック関数)
  // task.resume() を実行するとリクエストが行われ、
  // レスポンスが返ってきた時点でここの処理が非同期で行われる
}
// リクエストを実行
task.resume()

data, urlResponse, error の処理

dataにはレスポンスボディ、urlResponseにはレスポンスヘッダ、errorはレスポンスが得られなかったときのエラーが入ってくる。

  • data, urlResponseがnilのときのみerrorに値が入っている。
  • ホストが見つからない、通信エラーなどのそもそもAPIレスポンスが得られなかった場合はdataとurlResponseがnilになり、errorに値が入る。
  • ホストに接続できたが404エラー、500エラーなどの場合はdata, urlResponseが帰り、errorはnilが返ってくる
  • data, urlResponse, error がともにerrorになることはない。

ステータスコードはどうやって取るか

  • statusCodeはurlResponseに入っているが、そのままではstatusCodeは取れない
  • HTTPURLResponse extends URLResponse で、HTTPリクエストのときはHTTPURLResponse型が変えてくるので、キャストしてやる必要がある。
  • キャストしてやる必要がある。
let task = URLSession.shared.dataTask(with: request) { (data, urlResponse, error) in
  guard let urlResponse = urlResponse as? HTTPURLResponse else {
    // レスポンスが得られなかった場合
    return;
  }
  print(urlResponse.statusCode)
}

guard文については詳しく説明しないが、Optionalな値のOptionalを外すための構文だ。この場合、urlResponseがHTTPURLResponseにキャストできなかった場合はnilだった場合はguard文のelseの中に入る。

JSONDecoderを使ってJSONのレスポンスをいい感じにstructにマッピング

Codableというのをextendsしたstructを利用すると、Data <-> structの変換がめちゃめちゃ簡単にできる。

NasaAPIのレスポンスはざっくりこんな感じ(一部のレスポンスのみ表示)

{
  "collection": {
    "items":[
      {
        "data":[
          {
            "nasa_id":"nasaid"
          }
        ],
        "href":"http://nasa.gov/item.href"
      }
    ]
  }
}

この構造に対応したstructを定義する。例えばこうである。

public struct NasaImageApiSuccessResponse : Codable {
    let collection: NasaImageApiCollection
}

public struct NasaImageApiCollection : Codable {
    let items: [NasaImageApiItem]
}

public struct NasaImageApiItem : Codable {
    let data: [NasaImageApiItemData]
}

public struct NasaImageApiItemData : Codable {
    let nasaId: String

    /// APIの結果はsnake_caseだがそれをcamelCaseにマッピングする
    private enum CodingKeys: String, CodingKey {
        case nasaId = "nasa_id"
    }
}

じっくり比べてみれば、structが対応しているのがわかるだろう。必ずしも全部の要素をもたせる必要はない。APIのレスポンスのうち利用するものだけをstructでモデリングすればよい。

また、APIのレスポンスはsnake_caseだがコードではcamelCaseを使いたいという場合がある。その際はNasaImageApiItemDataのようにCodingKeysを定義してやれば変換される。

ではこのStructを使って実際に変換してみる。

let task = URLSession.shared.dataTask(with: request) { (data, urlResponse, error) in
  // nilチェック
  guard let data = data, let urlResponse = urlResponse as? HTTPURLResponse else {
    // 通信エラーなどの場合
    return;
  }
  do {
    let decoder = JSONDecoder()
    let response = try decoder.decode(NasaImageApiSuccessResponse.self, from: data)
    print(response.collection.items[0].data[0].nasaId)
  } catch {
    // レスポンスのJSONがstructと異なる場合など
    return;
  }
}

きれい簡単でわかりやすい。JSONのパースに失敗したりする場合もあるので、do-try-catch が必要だ。

ここまでのまとめ

Swift4でAPI Clientを実装するにはURLSessionとCodableを利用するときれいに実装できる。

しかしこれだけではAPI Clientクラスは実装できない。API Clientクラスの利用側に使いやすい形でレスポンスを返してやる必要がある。

時間は非同期コールバックと、セマフォにより同期的処理について書く。