当编码器没有变异函数时,编码数据如何最终进入编码器?

发布于 2025-01-13 06:51:47 字数 1760 浏览 0 评论 0原文

我正在尝试编写自定义 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 {}
}

您会注意到,它们都不会发生变化。他们都会返回一些东西,仅此而已。

概括而言,容器本身具有将特定类型编码到自身中的子函数:

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

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

func encode(to: Encoder) throws {}

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

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


我还查看了这个问题并且它的答案,尽管它们帮助了我,但它们显示了相同的行为,神奇地将数据带上链,只不过它传递的是自定义类型而不是 Encoder 本身。

I am trying to code a custom Coder and I am having trouble understanding something, particularly about the Encoder part.

The Encoder is only required to have three functions and two variables :

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 {}
}

You will notice, none of them are mutating. They all return something and that's it.

The containers themselves, to generalise, have sub functions to encode specific types into themselves :

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

If a user wants to customise how their class is encoded, they may do this :

func encode(to: Encoder) throws {}

And here is my problem ! The encoder persists after the call to encode(to: Encoder) but it does not mutate so how does the encoded data end up inside it ? Reading the JSON Encoder's source I can see that their containers have a variable containing an Encoder, which is passed down. 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 !

Is the Encoder somehow secretly passed as a reference instead of as a value ? That seems like the only logical conclusion but it seems very weird to me... If not, what is going on ?


I have also taken a look at this question and its answer, and although they have helped me they display the same behaviour of magically bringing data up the chain, except it passes down a custom type instead of the Encoder itself.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

耶耶耶 2025-01-20 06:51:47

Encoder 是否以某种方式作为引用而不是作为值秘密传递?

接近,但不完全是。 JSONEncoder 及其底层容器是用于基于引用的存储的相对较薄的单板接口。在非 Darwin 平台上使用的 swift-corelibs-foundation 实现中,这是使用 RefArrayRefObject存储带密钥的对象;在 Darwin 平台上,使用 NSMutableArrayNSMutableDictionary。正是这些引用对象被传递和共享,因此虽然各个编码器和容器可以被传递,但它们正在写入共享存储。

但它应该像 Swift 中的其他所有内容一样是写时复制的,这意味着数据不应该能够返回链!

对此还要注意一点:此实现内部的所有相关类型都是类(例如 JSONEncoderImpl 在 swift-corelibs-foundation 中,_JSONEncoder on Darwin),这意味着它们不会是写时复制的,而是通过引用传递反正。


关于您的编辑:这与链接的答案与其fileprivate最终类数据 — 这是在所有内部类型之间共享并传递的

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

Close, but not quite. JSONEncoder and its underlying containers are relatively thin veneer interfaces for reference-based storage. In the swift-corelibs-foundation implementation used on non-Darwin platforms, this is done using RefArray and RefObject to store keyed objects; on Darwin platforms, the same is done using NSMutableArray and NSMutableDictionary. It's these reference objects that are passed around and shared, so while the individual Encoders and containers can be passed around, they're writing to shared storage.

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!

One more note on this: all of the relevant types internal to this implementation are all classes (e.g. JSONEncoderImpl in swift-corelibs-foundation, _JSONEncoder on Darwin), which means that they wouldn't be copy-on-write, but rather, passed by reference anyway.


Regarding your edit: this is the same approach that the linked answer uses with its fileprivate final class Data — it's that class which is shared between all of the internal types and passed around.

丑疤怪 2025-01-20 06:51:47

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 作为参数。

Encoder 是否以某种方式作为引用而不是作为值秘密传递?

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

但一般来说,Encoder 实现也可以是 struct,并按值传递。毕竟,它所需要做的就是提供可以将数据编码到其中的可变容器。只要某处有一个类,就可以“看似”改变一个结构,而无需变异或使该结构成为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

JSONEncoder.encode uses JSONEncoderImpl as the concrete implementation of 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 would then eventually call encode(to: Encoder) on your Codable type with self as the argument.

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

Notice that JSONEncoderImpl is a class, so there is nothing "secret" about this at all. The encoder is passed as a reference in this particular case.

In general though, the Encoder implementation could also be a struct, and be passed by value. After all, all it needs to do is to provide mutable containers into which data can be encoded. As long as you have a class somewhere down the line, it is possible to "seemingly" mutate a struct without mutating or making the struct a var. Consider:

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文