SwiftのCodableのチュートリアルについてまとめました [翻訳]

Codableのページの翻訳

勉強がてらでやりました。 今回はSwift4で導入されたCodableについてのチュートリアルがありましたので下記のページを翻訳しました。
QiitaでもCodableがとても人気なので情報のまとめとしてはいいかと思いました。

Encoding, Decoding and Serialization in Swift 4

以下、翻訳内容です。

翻訳が芳しくなかったりプログラミング的な表現がおかしい箇所があるかもしれませんので、 お気軽にご指摘ください。

(個人的にはrepresentation(=表現)が合ってるかわかりませんでした。)

Swift 4でのエンコーディング、デコード、シリアル化について

これはSwift 4のために完全に更新されたいくつかの新しい章を含むベストセラーのSwift Apprenticeからまとめた章です。 このチュートリアルは、iOS 11 Launch Partyの一部として提供されています。楽しんでください!

データをファイルに保存するか、またはネットワーク経由で送信する必要があるいくつかのシナリオがあります。 このチュートリアルでは、インスタンスを文字列やバイトのストリームなどと言った別の表現に変換することで、これらのタスクを達成する方法を学習します。 このプロセスはエンコーディングとしても知られるし、リアライゼーションとして知られています。

データをインスタンスに変換する逆のプロセスは、デコードまたはデシリアライゼーションと呼ばれます。

ファイルに書き込むインスタンスがあるとします。 インスタンス自体はそのままファイルに書き込むことができないので、バイトのストリームのように別の表現にエンコードする必要があります。

encode.png

データがエンコードされてファイルに保存されたら、デコーダを使用していつでもインスタンスに戻すことができます。

decode.png

Encodable and Decodable Protocols (エンコードやデコードができるプロトコル)

Encodableプロトコルは、別の表現にエンコードできる型によって使用されます。 それは単一のメソッドを宣言します:

func encode(to: Encoder) throws

その型のすべてのストアドプロパティがEncodableにも準拠する場合、コンパイラによって生成されます。 これについては後ほどチュートリアルで学習します。

Decodableプロトコルは、デコード可能な型によって使用されます。 それはただ一つの初期化を宣言します:

init(from decoder: Decoder) throws

このチュートリアルの最後で、これらのメソッドを実装するタイミングと方法がわかります。

What is Codable? (Codableとは何か)

Codableとは、エンコードやデコードが可能であることを宣言するために型が準拠することができるプロトコルです。これは、基本的にエンコード可能なプロトコルとデコード可能なプロトコルの別名(=alias)です。

typealias Codable = Encodable & Decodable

Automatic Encoding and Decoding(エンコードとデコードの自動化)

Swiftには、標準ライブラリとFoundationフレームワークからInt、String、Date、Arrayや他にも数多くの型が用意されています。 型をcodable(コード化)にするには、Codableに準拠させ、すべての格納プロパティもcodable(コード化)であることを確認するのが最も簡単な方法です。

たとえば、おもちゃ工場を所有していて、従業員(Employee)データを格納するこの構造体があるとします。

struct Employee {
    var name: String
    var id: Int
}

この型をエンコードしたりデコードできるようにするために必要なのは、以下のようにCodableプロトコルに従うことだけです。

struct Employee: Codable {
    var name: String
    var id: Int
}

うわー、それは簡単でした! name(String)とid(Int)の両方がコード可能であるため、これを実行できました。

既にCodableな型のみを使用している場合は、これがうまく動作します。 しかし、あなたの型に、プロパティとして他のカスタムな型が含まれている場合はどうでしょうか? たとえば、Employee構造体を見て、それにはfavoriteToyプロパティもあるとします。

struct Employee: Codable {
    var name: String
    var id: Int
    var favoriteToy: Toy
}

struct Toy: Codable {
    var name: String
}

Toyが*Codableにも準拠していることを確認することで、EmployeeCodable**もまた全体的な適合性を維持します。

ArrayDictionaryなどのすべてのコレクション型には、codableな型が含まれている場合はcodableです。

Encoding and Decoding Custom Types (カスタムな型のエンコードとデコード処理)

XMLやプロパティリストなど、エンコードまたはデコードできる表現がいくつかあります。このセクションでは、SwiftのJSONEncoderクラスとJSONDecoderクラスを使用して、JSONエンコードとデコードの方法を学習します。

JSONJavaScript Object Notationの略で、データをシリアライズする最も一般的な方法の1つです。 これは人間が簡単に読むことができ、コンピュータが解析して生成することが容易です。

たとえば、Employee型のインスタンスJSONエンコードする場合、次のようになります。

{ "name": "John Appleseed", "id": 7 }
{
    "name": "John Appleseed",
    "id": 7
}

EmployeeインスタンスJSONシリアライズされる前の様子を簡単に理解できます。

JSONEncoder and JSONDecoder

コード化可能な型を取得したら、JSONEncoderを使用して型を、ファイルに書き込むかまたはネットワーク経由で送ることができる、データに変換します。 このEmployeeのインスタンスがあるとします。

let toy1 = Toy(name: "Teddy Bear");
let employee1 = Employee(name: "John Appleseed", id: 7, favoriteToy: toy1)

ジョンの誕生日が近づいてきて、あなたは彼に彼のお気に入りのおもちゃを贈り物として与えたいと思っています。 このデータをGifts部門に送信する必要があります。 これを行う前に、次のようにエンコードする必要があります。

let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(employee1)

encode(_ :)が失敗してエラーが出る可能性があるので、tryを使用する必要があることがわかります。

このようにjsonDataを印刷しようとすると:

print(jsonData)

Xcodeがデータを省略し、jsonDataのバイト数のみを提供することがわかります。 jsonDataにはemployee1の解読不能な表現が含まれているので、これは問題ありません。 このJSONの読み取り可能なバージョンを文字列(String)として作成する場合は、Stringで初期化処理をします。

let jsonString = String(data: jsonData, encoding: .utf8)
print(jsonString)
// {"name":"John Appleseed","id":7,"favoriteToy":{"name":"Teddy Bear"}}

これで特別なギフトAPIを使用してjsonDataまたはjsonStringをギフト部門に送ることができます。

JSONデータをデコードしてインスタンスに戻したい場合は、JSONDecoderを使用する必要があります。

let jsonDecoder = JSONDecoder()
let employee2 = try jsonDecoder.decode(Employee.self, from: jsonData)

コンパイラはこれを自ら把握することができないため、JSONをどの型でデコードするのかをデコーダに知らせる必要があることに注意してください。

Renaming Properties With CodingKeys (CodingKeysを使ったプロパティのリネーム)

ギフト部門のAPIでは、employeeのIDがidの代わりにemployeeIdとして表示される必要があることが判明しました。 幸運なことに、Swiftはこの種の問題の解決方法を提供します。

CodingKey Protocol, CodingKeys Enum

シリアル化された形式がAPIの要件と一致しない場合に備えて、CodingKeyプロトコルに準拠するCodingKeysの列挙型を使用すると、特定のプロパティの名前を変更できます。

CodingKeysを次のようにネストした列挙型に追加します。

struct Employee: Codable {
  var name: String
  var id: Int
  var favoriteToy: Toy

  enum CodingKeys: String, CodingKey {
    case id = "employeeId"
    case name
    case favoriteToy
  }
}

ここで注意すべき点がいくつかあります。

  1. CodingKeysは、あなたの型のネストした列挙型です。
  2. CodingKeyに準拠している必要があります。
  3. キーは常に文字列なので、生の型としてStringも必要です。
  4. 名前を変更する予定がない場合でも、すべてのプロパティをenumに含める必要があります。
  5. デフォルトでは、このenumコンパイラによって作成されますが、キーの名前を変更する必要がある場合は自分で実装する必要があります。

JSONをプリントすると、保存されたプロパティidのキーがemployeeIdに変更されたことがわかります。

{ "employeeId": 7, "name": "John Appleseed", "favoriteToy": {"name":"Teddy Bear"}}
{
    "employeeId": 7,
    "name": "John Appleseed",
    "favoriteToy": {
        "name": "Teddy Bear"
    }
}

Manual Encoding and Decoding(手動でのエンコードとデコード)

ギフト部門にデータを送信しようとすると、データが拒否されます。 今回は、employeeに送るギフトの情報がネストされた型の内部にあるべきではなく、giftと呼ばれるプロパティーとして存在すべきだと主張しています。 JSONは実際には次のようになります。

{ "employeeId": 7, "name": "John Appleseed", "gift": "Teddy Bear" }
{
    "employeeId": 7,
    "name": "John Appleseed",
    "gift": "Teddy Bear"
}

この場合、プロパティの名前を変更するだけでなく、JSONの構造を変更する必要があるため、CodingKeysを使用することはできません。 独自のエンコードおよびデコードのロジックを作成する必要があります。

The encode Function (エンコードの関数)

前述のチュートリアルで説明したように、Codableは実際にはEncodableプロトコルDecodableプロトコルのtypealias(タイプエイリアス)です。 encode(to:Encoder)を実装し、各プロパティをエンコードする方法を記述する必要があります。

それは複雑に聞こえるかもしれませんが、とてもシンプルです。 まず、favoriteToyの代わりにgiftのキーを使用するためにCodingKeysを更新します。

enum CodingKeys: String, CodingKey {
  case id = "employeeId"
  case name
  case gift
}

次に、EmployeeCodableへの適合性を削除し、次にこのextentionを追加する必要があります。

extension Employee: Encodable {
  func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(name, forKey: .name)
    try container.encode(id, forKey: .id)
    try container.encode(favoriteToy.name, forKey: .gift) // <- ココ
  }
}

まず、エンコーダのcontainerを返します。 これは、キーを使用してアクセスできるエンコーダの格納領域のviewです。 どのkeysをエンコードするかを選択する方法に注意してください。 重要なことは、favoriteToy.name.giftのキーに展開します。

今すぐ停止すると、次のようなエラーが表示されます。

'Employee' does not conform to expected type 'Decodable'

これは、Codableへの適合性を削除し、Encodableへの適合性のみを追加したためです。 今のところ、jsonStringemployee2にデコードするコードをコメントアウトできます。

もう一度jsonStringをprintすると、次のようになります。

{"name":"John Appleseed","gift":"Teddy Bear","employeeId":7}

成功しました。

The decode Function (デコードの関数)

データがギフト部門に届くと、システム内でこのJSONインスタンスに変換する必要があります。 このためにはデコーダが必要です。

以下のコードをplaygroundに追加して、EmployeeDecodable(したがってCodableにも)に準拠させます。

extension Employee: Decodable {
  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    name = try values.decode(String.self, forKey: .name)
    id = try values.decode(Int.self, forKey: .id)
    let gift = try values.decode(String.self, forKey: .gift)
    favoriteToy = Toy(name: gift)
  }
}

ここでは、デコーダのキー付きストレージコンテナを使用してencodeメソッドで行ったこととはかなり逆のことをしています。

Key Points

  • インスタンスをファイルに保存するかウェブに送る前にインスタンスエンコードする(またはシリアル化)必要があります。
  • 型はコーディング可能にするためにCodableプロトコルに準拠する必要があります。 すべてのプロパティがコード化可能である場合、その型は自動的にコード化可能です。
  • JSONは、最新のアプリケーションやWebサービスで最も一般的なエンコーディングです。JSONEncoderJSONDecoderを使用して、JSONから型のエンコードとデコードを行うことができます。