Swift5 支持自定义编码的三种容器-II

之前看到过的UnkeyedEncodingContainer也是一个protocol,它的定义在这里。除了SingleValueEncodingContainer约束的方法之外,它额外约束了下面这些方法:

public protocol UnkeyedEncodingContainer {
  var count: Int { get }

  mutating func encodeConditional(_ object: T) throws

% for type in codable_types:
  /// Encodes the elements of the given sequence.
  ///
  /// - parameter sequence: The sequences whose contents to encode.
  /// - throws: An error if any of the contained values throws an error.
  mutating func encode(
    contentsOf sequence: T) throws where T.Element == ${type}
% end

  mutating func encode(
    contentsOf sequence: T) throws where T.Element : Encodable

  mutating func nestedContainer(
    keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer

  mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer

  mutating func superEncoder() -> Encoder
}

其中:

  • count很简单,表示这个unkeyed container中目前编码进来的值个数;
  • encodeConditional我们姑且把它就当成是一般的encode方法就行了,因为官方也为这个方法提供了默认实现,这个默认实现,就是直接调用了普通的encode方法;

encode(contentsOf:)

接下来的一组encode(contentsOf:)用于把一系列内建类型和遵从了Encodable的类型编码到container里。其实呢,没有这些方法也没关系,我们自己遍历序列然后逐个编码就行了,不过有了它们呢,用起来就方便一些。这些方法的默认实现,在这里:

% for type in codable_types:
  public mutating func encode(
    contentsOf sequence: T) throws where T.Element == ${type}
  {
    for element in sequence {
      try self.encode(element)
    }
  }
% end

public mutating func encode(
  contentsOf sequence: T) throws where T.Element : Encodable
{
  for element in sequence {
    try self.encode(element)
  }
}

看到了吧,跟我们说的是一样的。为了体验这些方法的用法,我们给之前的User添加一个新的属性:

struct User {
  let name: String
  let age: Double
  var tags: [String]
}

把之前的Encodable扩展也进行对应的修改:

extension User: Encodable {
  private enum CodingKeys: CodingKey {
    case name
    case age
    case tags
  }

  public func encode(to encoder: Encoder) throws {
    var container = encoder.unkeyedContainer()
    try container.encode(name)
    try container.encode(age)
    try container.encode(contentsOf: tags)
  }
}

看到这个contentsOf的用法了吧,这样就可以把tags中的值,和name / age一起,编码到同一个数组里了。我们用下面的代码试一下:

let elev = User(name: "11", age: 11, tags: ["swifter", "gamer"])
let data = try JSONEncoder().encode(elev)
let str = String(bytes: data, encoding: .utf8)!

print(str)

执行下,就可以看到["11",11,"swifter","gamer"]这样的结果了。至于最后的nestedContainernestedUnkeyedContainersuperEncoder,我们得先放饭,等把所有类型的container都看完之后,再来说它们的实现。

至此,UnkeyedEncodingContainer约束的内容就说完了。接下来,我们得回到__JSONEncoder,来看看unkeyedContainer()这个方法究竟做了什么。

__JSONEncoder

unkeyedContainer的实现,定义在这里:

public func unkeyedContainer() -> UnkeyedEncodingContainer {
  let topContainer: NSMutableArray

  if self.canEncodeNewValue {
    topContainer = self.storage.pushUnkeyedContainer()
  } else {
      guard let container =
        self.storage.containers.last as? NSMutableArray else {
        preconditionFailure(
          "Attempt to push new unkeyed encoding container when already previously encoded at this path.")
      }

      topContainer = container
  }

  return _JSONUnkeyedEncodingContainer(
    referencing: self,
    codingPath: self.codingPath,
    wrapping: topContainer)
}

简单来说,就是从self.storage中获得一个NSMutableArray,然后用这个数组、__JSONENcoder对象自身以及codingPath,创建了一个_JSONUnkeyedEncodingContainer对象。

_JSONUnkeyedEncodingContainer

而这个_JSONUnkeyedEncodingContainer,就是我们要找的那个遵从了UnkeyedEncodingContainer的类型。它的定义在这里。结合刚才看到的创建方法,我们来看下它的属性:

fileprivate struct _JSONUnkeyedEncodingContainer : UnkeyedEncodingContainer {
  private let encoder: __JSONEncoder
  private let container: NSMutableArray
  private(set) public var codingPath: [CodingKey]

  public var count: Int {
      return self.container.count
  }

  fileprivate init(
    referencing encoder: __JSONEncoder,
    codingPath: [CodingKey],
    wrapping container: NSMutableArray) {
      self.encoder = encoder
      self.codingPath = codingPath
      self.container = container
  }
}

看到了吧,encoder是我们注入的__JSONEncoder对象,container是一个空的NSMutableArraycodingPath__JSONEncoder对象中的codingPath

接下来,就是UnkeyedEncodingContainer中约束的那一堆encode方法了,我们从中找一些具有代表性的来看看:

public mutating func encode(_ value: Bool) throws {
  self.container.add(self.encoder.box(value))
}

public mutating func encode(_ value: Double) throws {
  self.encoder.codingPath.append(_JSONKey(index: self.count))
  defer { self.encoder.codingPath.removeLast() }
  self.container.add(try self.encoder.box(value))
}

public mutating func encode(_ value: T) throws {
  self.encoder.codingPath.append(_JSONKey(index: self.count))
  defer { self.encoder.codingPath.removeLast() }
  self.container.add(try self.encoder.box(value))
}

而选择的依据,就是:

  • 编码不会抛出异常的内建类型;
  • 编码可能抛出异常的内建类型;
  • 编码任意一个遵从了Encodable的类型;

聊聊codingPath

其实,编码数据的核心逻辑和SingleValueEncodingContainer是一样的,都是把值打包之后,放到容器里。只不过,当打包的值可能抛出异常的时候,存入容器之前,也把对应值的Key存到了codingPath里。但SingleValueEncodingContainer中却没有这样做,我们对比下其编码Double的代码就会看到了:

public func encode(_ value: Double) throws {
  assertCanEncodeNewValue()
  try self.storage.push(container: self.box(value))
}

这是为什么呢?要说在这两种容器里打包数据的唯一区别,就是SingleValueEncodingContainer的打包结果,是直接放在__JSONEncoder.storage中的,而UnkeyedEncodingContainer,则是在storage中新开辟了一个NSMutableArray,并把打包的结果放在了这里。把它们的区别用一张图表示,就是这样的:

image

当我们使用SingleValueEncodingContainer的时候,一来,只能存储一个值;二来,这个值会直接存储在__JSONENcoder.storage里,因此,我们完全不需要额外记录这个值在storage中的位置。

UnkeyedEncodingContainer就不同了。我们可以在这个container里放两类东西:

  • 一类是打包进来的值;
  • 另一类是新创建的嵌套容器;

无论是那种情况,如果其中某一个值打包的时候可能发生异常,为了可以在box方法里获得这个位置,我们就得通过__JSONEncoder.codingPath不断的跟踪当前正在打包的值在容器中的位置。这就是为什么这两个容器中,对Double编码处理方式不同的原因了。

你可能感兴趣的:(Swift5 支持自定义编码的三种容器-II)