当编码数据没有变异函数时,它如何在编码器中结束?
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
我正在尝试编写自定义代码 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