我们来研究构成__JSONEncoder
的另一半,也就是它的Encoder
身份。
Encoder约束了什么
既然如此,我们当然应该从Encoder
究竟约束了什么开始。这个protocol
的定义在这里:
public protocol Encoder {
var codingPath: [CodingKey] { get }
var userInfo: [CodingUserInfoKey : Any] { get }
func container<Key>(keyedBy type: Key.Type)
-> KeyedEncodingContainer<Key>
func unkeyedContainer() -> UnkeyedEncodingContainer
func singleValueContainer() -> SingleValueEncodingContainer
}
其中,codingPath
和userInfo
在之前我们已经看过了。现在的重点,就是Encoder
约束的这三个方法,或者说,是这三个方法的返回值:KeyedEncodingContainer<Key> / UnkeyedEncodingContainer / SingleValueEncodingContainer
。这些叫做container的类型究竟是做什么的呢?其实它们的名字远比它们实际的作用看起来要高级多了。为了感性了解下这三个容器类型的作用,我们先来看个例子,假设要编码下面这个自定义的类型:
struct User {
let name: String
let age: Double
}
我们姑且不说Swift可以为这种类型自动添加Encodable
能力这回事儿(因为它的每个属性都遵从Encodable
)。现在,就是得自定义它的编码方法,第一种方法,就是用KeyedEncodingContainer
:
extension User: Encodable {
private enum CodingKeys: CodingKey {
case name
case age
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(age, forKey: .age)
}
}
用下面的代码测试一下:
let elev = User(name: "11", age: 11)
let data = try JSONEncoder().encode(elev)
let str = String(bytes: data, encoding: .utf8)!
print(str)
就可以得到{"name":"11","age":11}
这样的结果了。参照这个结果以及刚才encode(to:)
的实现,不难猜到,所谓的KeyedEncodingContainer
,就是可以让我们按照CodingKeys
中指定的key,把要编码的值存起来的容器。至于这个CodingKey
类型,我们找后找个机会再来说它。现在只要知道用它给container定义key的名字就行了。
接下来,再来看用UnkeyedEncodingContainer
来编码User
对象会如何,我们把之前的代码改成这样:
public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(name)
try container.encode(age)
}
执行之前相同的测试代码,就会得到这样的结果:["11",11]
。看到了吧,既然是UnKeyed
,那就是结果中只有值,没有key了呗。这样编码出来的,就是一个只包含值的数组。
至于最后一个SingleValueEncodingContainer
,它表示的就是一个编码单一值的容器,我们只能向其中添加一个值:
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(name)
/// Uncomment the following line will cause runtime error.
/// try container.encode(age)
}
就像上面注释的一样,如果去掉编码age
的注释,就会发生运行时错误。但即便是这样,我们之前使用的测试代码还是无法正常工作。因为通过SingleValueEncodingContainer
编码出来的结果,是一个NSString
,而不是一个NSDictonary
或者NSArray
。上一节,我们分析encode()
方法的时候就看到了,如果box_
返回的不是一个NS
开头的集合类型,都会发生运行时错误。因此,如果我们把User
用了SingleValueEncodingContainer
编码,那么测试的时候,就得用这个值手工构建一个集合类型:
let elev = User(name: "11", age: 11)
let data = try JSONEncoder().encode([elev])
let str = String(bytes: data, encoding: .utf8)!
print(str)
这样,就可以得到["11"]
这样的结果了。不过这只是一个用于演示的例子,我们当然不会只编码User
对象的某一个属性。
看过了这三个例子之后,现在至少可以感性的知道了,如果把JSONEncoder
比喻成一台加工数据的机器,所谓Encoder
约束的这些方法返回的containers,就好比为这个机器提交的加工规格表单,我们可以填写key-value形式的,可以添加数组形式的,也可以填写加工成一个单一值形式的。
__JSONEncoder中container的实现
理解了刚才这些例子,我们就可以反着追回去看看__JSONEncoder
中,这三个方法究竟是如何实现的了。
SingleValueEncodingContainer
我们从最简单的singleValueContainer
方法说起,它的定义在这里:
fileprivate class __JSONEncoder : Encoder {
public func singleValueContainer()
-> SingleValueEncodingContainer {
return self
}
}
看到了吧,就是返回它自己,也就是说,__JSONEncoder
自身就是一个SingleValueEncodingContainer
。那这个SingleValueEncodingContainer
又是什么呢?实际上,它是一个定义在这里的protocol
:
public protocol SingleValueEncodingContainer {
var codingPath: [CodingKey] { get }
mutating func encodeNil() throws
% for type in codable_types:
mutating func encode(_ value: ${type}) throws
% end
mutating func encode<T : Encodable>(_ value: T) throws
}
也就是说,它为nil
,codable_types
中定义的类型以及任何一个遵从了Encodable
的类型,定义了一个编码它们的encode
方法。既然__JSONEncoder
遵从了SingleValueEncodingContainer
,那我们接下来当然就是去看看__JSONEncoder
是如何实现这些方法的了。这个extension
的定义,在这里。对于protocol
中约束的每一类encode
,我们各列举一个实现:
extension __JSONEncoder : SingleValueEncodingContainer {
// MARK: - SingleValueEncodingContainer Methods
fileprivate func assertCanEncodeNewValue() {
precondition(self.canEncodeNewValue,
"Attempt to encode value through single value container when previously value already encoded.")
}
public func encodeNil() throws {
assertCanEncodeNewValue()
self.storage.push(container: NSNull())
}
public func encode(_ value: Bool) throws {
assertCanEncodeNewValue()
self.storage.push(container: self.box(value))
}
public func encode<T : Encodable>(_ value: T) throws {
assertCanEncodeNewValue()
try self.storage.push(container: self.box(value))
}
}
其中,canEncodeNewValue
的实现在这里:
fileprivate var canEncodeNewValue: Bool {
return self.storage.count == self.codingPath.count
}
其实,在官方源代码里,这个计算属性有一大段注释,大家可以稍后自己去看看。现在,只要知道,所谓可以编码新值的要求,就是__JSONEncoder.storage
中元素的个数和codingPath
中元素的个数相同就行了。
至于是编码nil
、codable_types
中定义的类型还是任何一个遵从了Encodable
的类型,其实它们的逻辑都是一样的,把这些值用box
方法打包之后,存入__JSONEncoder.storage
就行了。
看到这,想必大家也就明白为什么SingleValueEncodingContainer
只能编码一个值了。在向storage
中存入元素之后,self.storage.count
的值会增加,但是编码进来的值并没有对应的codingPath
。当我们继续向其中编码新值的时候,canEncodeNewValue
就会返回false
,进而precondition
就会触发错误了。