しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

Codable+AlamofireでAPIのレスポンスをオブジェクトに変換、Swift4.1対応版

AlamofireとCodableを使った実装を試してみました。
Alamofireは事前にCocoaPodsでインストールしておきます。

Alamofire+Codableの事前準備

target 'MyApp' do
  use_frameworks!

  pod 'Alamofire'
end

もし非SSL(http)通信をする場合はApp Transport Security Settingsの設定を事前にしておいてください。

f:id:llcc:20180623224058p:plain

Alamofire+Codableの簡単な使い方

まずは下のような簡単な構造のデータをAPI経由で受け取ってみます。

{
    id: 1,
    name: 'my_name'
}

最初にCodableに準拠した構造体を作ります。

struct User: Codable {
    let id: Int
    let name: String
}

構造体を作ったらサーバーからのレスポンスを構造体に変換する処理を実装します。
実装は下の通りです。

この例ではgetでuserのデータを取得しています。

Alamofire.request("http://localhost:3000/users/1.json").response { response in
    if let data = response.data {
        let result = try? JSONDecoder().decode(User.self, from: data)
        print(result)
    }
}

ログを見ると無事にデータが取得できている事が分かります。

f:id:llcc:20180623224310p:plain

Codableの型が違う場合

下のようにresponseと構造体の型が違う場合の挙動も見てみます。

struct User: Codable {
    let id: Int
    let name1: String // name → name1に変更
}

下のようにtry?を使っている場合はnilが返ってきます。

let result = try? JSONDecoder().decode(User.self, from: data) // → nil

try!を使っている場合はアプリが落ちます。

let result = try! JSONDecoder().decode(User.self, from: data) // → クラッシュ

エラーメッセージは下の通りです。

Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "name1", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"name1\", intValue: nil) (\"name1\").", underlyingError: nil)): file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-902.0.54/src/swift/stdlib/public/core/ErrorType.swift, line 184
2018-06-23 22:45:49.750172+0900 MyApp[19279:6248046] Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "name1", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"name1\", intValue: nil) (\"name1\").", underlyingError: nil)): file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-902.0.54/src/swift/stdlib/public/core/ErrorType.swift, line 184

少し話は逸れますがレスポンスのキーが多い分にはエラーは起きません。

# レスポンス
{
  id: 1,
  name: 'my_name',
  name2: 'my_name',
  name3: 'my_name',
  name4: 'my_name',
}
let result = try? JSONDecoder().decode(User.self, from: data) // → 問題なくデコードできる

Codableで数字をDouble型として扱う

数値型はIntではなくDoubleで受け取る事もできます。
下のように変えるだけで問題なくデコードします。

struct User: Codable {
    let id: Double
    let name: String
}

ただし数値を文字列にする事はできません。

struct User: Codable {
    let id: String
    let name: String
}

Codableで複数オブジェクトをパースする

下のように複数データが返ってくる場合について見ていきます。

[{
  id: 1,
  name: 'my_name',
},{
  id: 2,
  name: 'my_name2',
},{
  id: 3,
  name: 'my_name3',
}]

複数のデータが返る時はUser.selfをArray.selfに置き換えるだけです。

Alamofire.request("http://localhost:3000/users.json").response { response in
    if let data = response.data {
        let result = try? JSONDecoder().decode(Array<User>.self, from: data) // User.selfをArray<User>.selfに置き換えただけ
        print(result)
    }
}

ログを見るとしっかりデコードされている事が分かります。

f:id:llcc:20180623225352p:plain

Codableでスネークケースのデータを受け取る

次のようにサーバーからのレスポンスはスネークケース、iOSアプリはキャメルケースの場合も見ていきます。

# レスポンスはスネークケース
{
  id: 1,
  name: 'my_name',
  book_count: 1
}
// 構造体はキャメルケース
struct User: Codable {
    let id: Int
    let name: String
    let bookCount: Int
}

こういった場合はJSONDecoderのkeyDecodingStrategyオプションを利用します。
keyDecodingStrategyに.convertFromSnakeCaseをセットする事でスネークケースとキャメルケースの変換をしてくれるようになります。

Alamofire.request("http://localhost:3000/users/1.json").response { response in
    if let data = response.data {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase // スネークケースとキャメルケースの変換をするオプション
        let result = try? decoder.decode(User.self, from: data)
        print(result)
    }
}

ログを見ると無事に変換できている事が分かります。

f:id:llcc:20180623230407p:plain

keyDecodingStrategyですがSwift4.1から登場したのでそれ以前のSwiftでは使えません。

CodableでDate型を扱う

下のようにサーバーがDate型のプロパティーを返す時も確認します。

{
  id: 1,
  name: 'my_name',
  created_at: '2018-01-01T00:00:00.000Z',
  updated_at: '2018-01-01T00:00:00.000Z'
}

まずはレスポンスに合った構造体を作ります。

struct User: Codable {
    let id: Int
    let name: String
    let createdAt: Date
    let updatedAt: Date
}

デコード処理は下の通りです。
JSONDecoderのdateDecodingStrategyというプロパティーにDateの形式をセットします。

Alamofire.request("http://localhost:3000/users/1.json").response { response in
    if let data = response.data {
        let decoder = JSONDecoder()
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
        decoder.dateDecodingStrategy = .formatted(formatter)
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        let result = try? decoder.decode(User.self, from: data)
        print(result)
    }
}

ログを見るとDateもしっかりと変換できている事が分かります。

f:id:llcc:20180624150506p:plain

Codableでネスト(入れ子)のデータを扱う

最後に下のようなネスト形式について見ていきます。

{
  id: 1,
  name: 'my_name',
  books: [
    { id: 1, title: 'title1' },
    { id: 2, title: 'title2' },
  ]
}

上のようなデータを受け取りたい場合は下のような構造体を作ります。

struct User: Codable {
    let id: Int
    let name: String
    let books: [Book]
}

struct Book: Codable {
    let id: Int
    let title: String
}

デコード部分は今までと同じ処理で問題ありません。

Alamofire.request("http://localhost:3000/users/1.json").response { response in
    if let data = response.data {
        let result = try? JSONDecoder().decode(User.self, from: data)
        print(result)
    }
}

実行してログを見るとUserのbooksプロパティーに値が入っている事が分かります。

f:id:llcc:20180624145316p:plain

まとめ

Swift4から登場したCodableですがかゆいところに手が届く感じでとても便利そうでした。
今まではObjectMapperがメインだったんですが今後はCodableも使ってみようと思います。