UnkeyedEncodingContainer 和 KeyedEncodingContainerProtocol 中的 superEncoder 有什么用?

What is superEncoder for in UnkeyedEncodingContainer and KeyedEncodingContainerProtocol?

很抱歉问这样一个基本问题,但我找不到任何地方的答案:

为了制作Encoder,您必须定义不同类型的容器:

最后两个必须都包含一个名为 superEncoder 的方法,但是我无法在任何地方找到它应该做什么。 has an answer that implements it but doesn't explain it, and this talk只是顺便提一下。

它应该做什么,有什么用?

编码器中的

superEncoder 和解码器中的 superDecoder 是一种能够在容器内“保留”嵌套容器的方法,而无需提前知道它将是什么类型。

这样做的主要目的之一是支持 Encodable/Decodable classes 中的继承:class T: Encodable 可以选择编码它的内容变成一个 UnkeyedContainer,但是它的子 class U: T 可以选择将它的内容编码成一个 KeyedContainer.

U.encode(to:) 中,U 将需要调用 super.encode(to:),并传入一个 Encoder — 但它不能传入 Encoder 它已收到,因为它已经以密钥方式对其内容进行了编码,并且 TEncoder 请求未密钥的容器是无效的。 (一般来说,U 甚至不知道 T 想要什么样的容器。)

然后,逃生舱口是 U 向其容器请求嵌套的 Encoder 以便能够将其传递给其超级class。容器将为嵌套值创建 space,并创建一个新的 Encoder,允许写入保留的 space。 T 然后可以使用嵌套的 Encoder 进行编码。

结果看起来好像 U 请求了一个嵌套容器并将 T 的值编码到其中。


为了更具体一点,请考虑以下内容:

import Foundation

class T: Encodable {
    let x, y: Int
    init(x: Int, y: Int) { self.x = x; self.y = y }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
        try container.encode(x)
        try container.encode(y)
    }
}

class U: T {
    let z: Int
    init(x: Int, y: Int, z: Int) { self.z = z; super.init(x: x, y: y) }
    
    enum CodingKeys: CodingKey { case z }
    
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(z, forKey: .z)
        
        /* How to encode T.x and T.y? */
    }
}

let u = U(x: 1, y: 2, z: 3)
let data = try JSONEncoder().encode(u)
print(String(data: data, encoding: .utf8))

U 有一些关于如何编码 xy:

的选项
  1. 它可以通过在其 CodingKeys 枚举中包含 xy 并直接对它们进行编码,从而真正覆盖 T 的编码策略。这忽略了 T 更喜欢编码的方式,如果需要 解码 ,则意味着您必须能够创建一个新的 T 没有调用它的init(from:)

  2. 它可以调用 super.encode(to: encoder) 让超级class 编码到它所做的 same 编码器。在这种情况下,这将 崩溃 ,因为 U 已经从 encoder 请求了一个带密钥的容器,而调用 T.encode(to:) 将立即请求一个未带密钥的容器来自同一个编码器

    • 一般来说,如果TU都请求相同的容器类型,这个可能有效,但是真的不建议依赖。根据 T 的编码方式,它可能会覆盖 U 已经编码的值
  3. 嵌套 T inside super.encode(to: container.superEncoder());这将在容器字典中保留一个位置,创建一个新的 Encoder,并让 T 写入该编码器。 JSON 中的结果将是:

    { "z": 3,
      "super": [1, 2] }