当编码数据没有变异函数时,它如何在编码器中结束?

How does the encoded data end up in the Encoder, when it has no mutating functions?

我正在尝试编写自定义代码 Coder,但我无法理解某些内容,尤其是 Encoder 部分。

Encoder只需要三个函数和两个变量即可:

struct MyEncoder : Encoder {
    public var codingPath : [CodingKey]
    public var userInfo : [CodingUserInfoKey : Any]
    public func container<Key>(keyedBy type: Key.Type -> KeyedEncodingContainer<Key> where Key : CodingKey {}
    public func unkeyedContainer() -> UnkeyedEncodingContainer {}
    public func singleValueContainer() -> SingleValueEncodingContainer {}
}

您会注意到,其中 none 个正在发生变异。他们都是 return 东西,仅此而已。

概括地说,容器本身具有将特定类型编码到自身中的子功能:

struct MySingleValueContainer : SingleValueEncodingContainer {
    var codingPath: [CodingKey]
    mutating func encode(_ value : someType) throws {}
    /// etc with a lot of different types
}

如果用户想要自定义其 class 的编码方式,他们可以这样做:

func encode(to: Encoder) throws {}

这是我的问题!编码器在调用 encode(to: Encoder) 后仍然存在,但它 不会发生变化 那么编码数据如何在其中结束?阅读 JSON Encoder's source 我可以看到他们的容器有一个包含 Encoder 的变量,它被传递了下来。但它应该像 Swift 中的其他所有内容一样是写时复制的,这意味着数据不应该能够回到链上!

Encoder 是否以某种方式秘密传递为引用而不是值?这似乎是唯一合乎逻辑的结论,但对我来说 非常奇怪 ...如果不是,那是怎么回事?


我也查看了 及其答案,虽然他们帮助了我,但他们展示了同样的行为,即神奇地将数据带到链上,除了它向下传递自定义类型而不是Encoder本身。

Is the Encoder somehow secretly passed as a reference instead of as a value?

接近,但不完全是。 JSONEncoder 及其底层容器是 reference-based 存储的相对较薄的单板接口。在 non-Darwin 平台上使用的 swift-corelibs-foundation 实现中,这是使用 RefArray and RefObject to store keyed objects; on Darwin platforms, the same is done using NSMutableArray and NSMutableDictionary 完成的。传递和共享的是 这些 引用对象,因此虽然可以传递单个 Encoder 和容器,但它们正在写入共享存储。

But it should be copy-on-write as everything else in Swift, which means the data shouldn't be able to make its way back up the chain!

关于此的另一个注意事项:此实现内部的所有相关类型都是 classes(例如 JSONEncoderImpl in swift-corelibs-foundation, _JSONEncoder 在 Darwin 上),这意味着它们 wouldn' t 是 copy-on-write,而是通过引用传递的。


关于您的编辑:这与 与其 fileprivate final class Data 使用的方法相同 — 这是共享的 class在所有内部类型之间传递并传递。

JSONEncoder.encode使用JSONEncoderImpl作为Encoder的具体实现(source):

open func encode<T: Encodable>(_ value: T) throws -> Data {
    let value: JSONValue = try encodeAsJSONValue(value)
    ...
}

func encodeAsJSONValue<T: Encodable>(_ value: T) throws -> JSONValue {
    let encoder = JSONEncoderImpl(options: self.options, codingPath: [])
    guard let topLevel = try encoder.wrapEncodable(value, for: nil) else {
        ...
    }

    return topLevel
}

wrapEncodable 最终会在您的 Codable 类型上调用 encode(to: Encoder),并以 self 作为参数。

Is the Encoder somehow secretly passed as a reference instead of as a value ?

请注意 JSONEncoderImpl 是一个 class,因此根本没有什么“秘密”。在这种特殊情况下,编码器 作为参考传递的。

但一般来说,Encoder 实现也可以是 struct,并按值传递。毕竟,它需要做的就是提供可变的容器,数据可以编码到其中。只要你有一个 class 某处 ,就可以“看似”改变一个没有 mutating 的结构或使结构成为 var.考虑:

private class Bar {
    var magic: Int = -1
}

struct Foo: CustomStringConvertible {
    private let bar = Bar()
    
    var description: String { "\(bar.magic)" }
    
    func magicallyMutateMyself() {
        bar.magic = Int.random(in: 0..<100)
    }
}

let foo = Foo()
print(foo) // -1
foo.magicallyMutateMyself()
print(foo) // some other number