LoginSignup
2
4

More than 3 years have passed since last update.

[Swift5]Codableソースコード解析①

Last updated at Posted at 2019-08-21

Codableから説明

関連するファイルは二つあります:

  1. gybにユーザーに公開しているprotocolおよびCodabelを実現しているSwiftのタイプ
  2. JSONEncoder.swift。JSONEncoderとJSONDecoderを実現しているファイルです。
    続きましては、Codableの定義はここです:
qiita.swift
public typealias Codable = Encodable & Decodable

Encodable

Encodableのタイプはなんでしょうか?実際、encode(to:)メソッドを提供しているだけです。

qiita.swift
public protocol Encodable {
  /// Encodes this value into the given encoder.
  ///
  /// If the value fails to encode anything, `encoder` will encode an empty
  /// keyed container in its place.
  ///
  /// This function throws an error if any values are invalid for the given
  /// encoder's format.
  ///
  /// - Parameter encoder: The encoder to write data to.
  func encode(to encoder: Encoder) throws
}

ここまで、三つの質問を思い出すと:
1.SwiftがサポートしているEncodableのタイプは全部このメソッドを実現していますか?
2.SwiftではデフォルトでEncodableをサポートしているメソッドはなんでしょうか?
3.Encoderは何ですか?

質問1の答えはイエスです。Codable.swift.gybファイルにデフォルトタイプの実現があります。
例えば、Intのデフォルト実現は:

qiita.swift
extension Int : Codable {
  public func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    try container.encode(self)
  }
}

Arrayのデフォルト実現は:

qiita.swift
extension Array : Encodable where Element : Encodable {
  public func encode(to encoder: Encoder) throws {
    var container = encoder.unkeyedContainer()
    for element in self {
      try container.encode(element)
    }
  }
}

なるほど、Swiftでは本当にEncodableのタイプはメソッドを実現しています。

そしたら、Swiftでは、Encodableをサポートしているタイプはどれくらいありますか?
Codable.swift.gybファイルに下記のコードがあります。

qiita.swift
%{
codable_types = ['Bool', 'String', 'Double', 'Float',
                 'Int', 'Int8', 'Int16', 'Int32', 'Int64',
                 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64']
}%

上記以外、ファイルの一番下に、Array / Set / Dictionary / Optionalなどもあります。

ここまで、質問3だけ残ってます。
encodeメソッドのEncoderはなんでしょうかね。ブラックボックスとして考えれば、結果的にSwiftのオブジェクトをJSON文字列にエンコディングされています。

JSONEncoder

JSONEncodeの定義:

  • JSONエンコーディング結果の出力フォマット(public struct OutputFormatting)
  • Dateタイプのエンコーディング方式(public enum DateEncodingStrategy)
  • Dataタイプのエンコーディング方式(public enum DataEncodingStrategy)
  • 例外的な浮動小数点エンコーディング方式(public enum NonConformingFloatEncodingStrategy)
  • JSONのkeyのエンコーディング方式(public enum KeyEncodingStrategy)

続きましては、JSONEncoderを使用してエンコーディングする時、デフォルト使用するプロパティ:

qiita.swift
open class JSONEncoder {
  open var outputFormatting: OutputFormatting = []

  open var dateEncodingStrategy: DateEncodingStrategy = .deferredToDate

  open var dataEncodingStrategy: DataEncodingStrategy = .base64

  open var nonConformingFloatEncodingStrategy:
    NonConformingFloatEncodingStrategy = .throw

  open var keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys
}

デフォルトのプロパティを便利に使う為、内部タイプの_Optionsと内部プロパティのoptionsも定義されています。

qiita.swift
open class JSONEncoder {
  fileprivate struct _Options {
    let dateEncodingStrategy: DateEncodingStrategy
    let dataEncodingStrategy: DataEncodingStrategy
    let nonConformingFloatEncodingStrategy:
      NonConformingFloatEncodingStrategy
    let keyEncodingStrategy: KeyEncodingStrategy
    let userInfo: [CodingUserInfoKey : Any]
  }

  fileprivate var options: _Options {
    return _Options(dateEncodingStrategy: dateEncodingStrategy,
      dataEncodingStrategy: dataEncodingStrategy,
      nonConformingFloatEncodingStrategy: nonConformingFloatEncodingStrategy,
      keyEncodingStrategy: keyEncodingStrategy,
      userInfo: userInfo)
  }
}

そして、JSONEncoderのコンストラクタ(only one):

qiita.swift
open class JSONEncoder {
  public init() {}
}

最後は、我々が使用するencodeメソッド:

qiita.swift
open func encode<T : Encodable>(_ value: T) throws -> Data

__JSONEncoder

これから、JSONEncoder.encodeメソッドの実現から見てみましょう。関数定義はここです。
実行ロジック:

qiita.swift
open func encode<T : Encodable>(_ value: T) throws -> Data {
  let encoder = __JSONEncoder(options: self.options)

  guard let topLevel = try encoder.box_(value) else {
    /// throw exception
  }

  /// Handle invalid box value.

  let writingOptions =
    JSONSerialization.WritingOptions(
      rawValue: self.outputFormatting.rawValue)
  do {
     return try JSONSerialization.data(
      withJSONObject: topLevel, options: writingOptions)
  } catch {
    /// throw exception
  }
}

encodeの実現:

  • まず、Encoderタイプの__JSONEncoderオブジェクトを作る
  • そして、box_メソッドでパラメーターvalueをJSONエンコーディングできるデーターに変換する
  • エラーチェックなどされたら、FoundationのJSONSerialization.dataを呼び出し、データーのエンコーディングできて、エンコーディング結果のDataオブジェクトをreturn
2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4