[Swift4]Codableが便利らしい

Posted on

codableが便利らしいと所々で聞くので、使ってみました

codableって何ができる?

  • 実運用の利点という話で言うと、APIなどから返ってきたデータを構造体に簡単に変換できるようになる
    • APIから返ってきたデータをJSONにSerializeしてからオブジェクトを生成みたいなのが楽になる
  • 実際にはCodableはプロトコルだから、JSONに限らず色々変換できる。Encoder,Decoderを実装すれば。今はJSONとPropertyListだけらしい

取得したデータをオブジェクト化する実装

取得したbitcoinの現在値をオブジェクト化してみます

レスポンスとして返ってくる値

{
    "best_ask" = 1197300;
    "best_ask_size" = "0.2809";
    "best_bid" = 1197020;
    "best_bid_size" = "0.706";
    ltp = 1197008;
    "product_code" = "BTC_JPY";
    "tick_id" = 1870648;
    timestamp = "2018-03-03T05:26:30.827";
    "total_ask_depth" = "2505.70016166";
    "total_bid_depth" = "3444.53170983";
    volume = "153817.90598893";
    "volume_by_product" = "14684.59241436";
}

こんなレスポンスを受けた時に構造体にデコードしてあげる 前に書いたbitcoinの価格を取得するコードを変更してみます。

部分的に抜き出すとこんな書き方をしていたのですが、

let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments)
// このあとjsonをDistionaryとかにキャストして、オブジェクトを生成する必要がある
// コードは省略

こんな感じで書けました。 これは楽だし、何回もつかいそうです。

struct Ticker: Codable {
    let best_ask: Int // ask = 買い
    let best_ask_size: Double
    let best_bid: Int //bid=売り
    let best_bid_size: Double
    let ltp: Int //最終取引価格
    let product_code: String
    let tick_id : Int
    let timestamp: String //Stringかは検討
    let total_ask_depth: Double
    let total_bid_depth: Double
    let volume: Double
    let volume_by_product: Double
}

func xxx(){
  var components = URLComponents(string:"https://lightning.bitflyer.jp/ticker")
        components?.queryItems = [URLQueryItem(name:"product_code",value:"BTC_JPY")]
        let url = components?.url

  let task = URLSession.shared.dataTask(with: url!){
              data,response,error in
              if let data = data, let response = response {
                  print(response)
                  let decoder:JSONDecoder = JSONDecoder()
                  do{
                      let ticker: Ticker = try decoder.decode(Ticker.self, from: data)
                      print(ticker)
                      print(ticker.best_ask)
                  }catch{
                      print(error.localizedDescription)
                  }
              }else{
                  print(error ?? "Error")
              }
          }
  }



try Swiftでこちらの発表をみたところ、良さげな実装が提案されている

http://niwatako.hatenablog.jp/entry/2018/03/02/153248

let decoder:JSONDecoder = JSONDecoder()
do{
    let ticker: Ticker = try decoder.decode(Ticker.self, from: data)
}catch{
    print(error.localizedDescription)
}

改良版の同部分はこれだけで動く。良さそう (参照元の資料ではtry!使ってるけど、きっと短くさせるため)

do {
    let ticker = try Ticker(_data: data)
}catch{
    print(error.localizedDescription)
}

方法としてはこんなプロトコルを予め定義しておく

protocol DataConvertible {
    init(_data: Data) throws
    func convertToData() throws -> Data
}

extension DataConvertible where Self: Decodable{
    init(_data: Data) throws{
        self = try JSONDecoder().decode(Self.self, from: _data)
    }
}

extension DataConvertible where Self: Encodable{
    func convertToData() throws -> Data {
        return try JSONEncoder().encode(self)
    }
}

struct Ticker: Codable, DataConvertible{
・・・



得た理解

  • Codableプロトコルは、dataがConvertできるよという印で、JSONDecoderに使えるようになる

    • きっとJSONDecoderの中では、JSONSerializeとかこれまで書いてたことをしてるんだろう
  • 一方、今回パクって書いたDataConvertibleプロトコルは、何かというと、Codableプロトコルには準拠している前提で、JSON変換のDataからのinitializerや、Dataへの変換は共通処理だからまとめましょうという話でしょう。

(以降はCodableと関係なく、僕が知らなかったことに対する得た知識)

  • protocolはこの変数、関数持ってるよ。という印と思っていたが、protocol extensionを使うと共通実装が追加できる
    • その時、Where句で型を指定して実装ができる
  • try!
    • エラーが出た時は落ちる
    • try?もあって、エラーが出た時はnilが変える
  • throwsついてる関数は、do{}の中でtryを付けて呼び出す
    • throwする例外はErrorプロトコルを実装している必要がある
    • (これは今度必要になるからそのとき書いてみよう)
  • catchしたときに暗黙的にError型のerrorという変数が使える