当编码器没有变异函数时,编码数据如何最终进入编码器?
我正在尝试编写自定义 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
接近,但不完全是。
JSONEncoder
及其底层容器是用于基于引用的存储的相对较薄的单板接口。在非 Darwin 平台上使用的 swift-corelibs-foundation 实现中,这是使用RefArray
和RefObject
存储带密钥的对象;在 Darwin 平台上,使用NSMutableArray
和NSMutableDictionary
。正是这些引用对象被传递和共享,因此虽然各个编码器
和容器可以被传递,但它们正在写入共享存储。对此还要注意一点:此实现内部的所有相关类型都是类(例如
JSONEncoderImpl
在 swift-corelibs-foundation 中,_JSONEncoder
on Darwin),这意味着它们不会是写时复制的,而是通过引用传递反正。关于您的编辑:这与链接的答案与其
fileprivate最终类数据 — 这是在所有内部类型之间共享并传递的类。
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 usingRefArray
andRefObject
to store keyed objects; on Darwin platforms, the same is done usingNSMutableArray
andNSMutableDictionary
. It's these reference objects that are passed around and shared, so while the individualEncoder
s and containers can be passed around, they're writing to shared storage.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.JSONEncoder.encode
使用JSONEncoderImpl
作为Encoder
的具体实现(source):wrapEncodable
最终会在您的Codable
类型上调用encode(to: Encoder)
以 self 作为参数。请注意,JSONEncoderImpl 是一个类,因此这根本没有什么“秘密”。在这种特殊情况下,编码器作为参考传递。
但一般来说,
Encoder
实现也可以是struct
,并按值传递。毕竟,它所需要做的就是提供可以将数据编码到其中的可变容器。只要某处有一个类,就可以“看似”改变一个结构,而无需变异
或使该结构成为var
。考虑:JSONEncoder.encode
usesJSONEncoderImpl
as the concrete implementation ofEncoder
(source):wrapEncodable
would then eventually callencode(to: Encoder)
on yourCodable
type withself
as the argument.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 astruct
, 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 withoutmutating
or making the struct avar
. Consider: