我们来研究构成__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就会触发错误了。