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という変数が使える