之前看到过的UnkeyedEncodingContainer
也是一个protocol
,它的定义在这里。除了SingleValueEncodingContainer
约束的方法之外,它额外约束了下面这些方法:
public protocol UnkeyedEncodingContainer {
var count: Int { get }
mutating func encodeConditional<T : AnyObject & Encodable>(_ object: T) throws
% for type in codable_types:
/// Encodes the elements of the given sequence.
///
/// - parameter sequence: The sequences whose contents to encode.
/// - throws: An error if any of the contained values throws an error.
mutating func encode<T : Sequence>(
contentsOf sequence: T) throws where T.Element == ${type}
% end
mutating func encode<T : Sequence>(
contentsOf sequence: T) throws where T.Element : Encodable
mutating func nestedContainer<NestedKey>(
keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey>
mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer
mutating func superEncoder() -> Encoder
}
其中:
-
count
很简单,表示这个unkeyed container中目前编码进来的值个数; -
encodeConditional
我们姑且把它就当成是一般的encode
方法就行了,因为官方也为这个方法提供了默认实现,这个默认实现,就是直接调用了普通的encode
方法;
encode(contentsOf:)
接下来的一组encode(contentsOf:)
用于把一系列内建类型和遵从了Encodable
的类型编码到container里。其实呢,没有这些方法也没关系,我们自己遍历序列然后逐个编码就行了,不过有了它们呢,用起来就方便一些。这些方法的默认实现,在这里:
% for type in codable_types:
public mutating func encode<T : Sequence>(
contentsOf sequence: T) throws where T.Element == ${type}
{
for element in sequence {
try self.encode(element)
}
}
% end
public mutating func encode<T : Sequence>(
contentsOf sequence: T) throws where T.Element : Encodable
{
for element in sequence {
try self.encode(element)
}
}
看到了吧,跟我们说的是一样的。为了体验这些方法的用法,我们给之前的User
添加一个新的属性:
struct User {
let name: String
let age: Double
var tags: [String]
}
把之前的Encodable
扩展也进行对应的修改:
extension User: Encodable {
private enum CodingKeys: CodingKey {
case name
case age
case tags
}
public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(name)
try container.encode(age)
try container.encode(contentsOf: tags)
}
}
看到这个contentsOf
的用法了吧,这样就可以把tags
中的值,和name / age
一起,编码到同一个数组里了。我们用下面的代码试一下:
let elev = User(name: "11", age: 11, tags: ["swifter", "gamer"])
let data = try JSONEncoder().encode(elev)
let str = String(bytes: data, encoding: .utf8)!
print(str)
执行下,就可以看到["11",11,"swifter","gamer"]
这样的结果了。至于最后的nestedContainer
,nestedUnkeyedContainer
和superEncoder
,我们得先放饭,等把所有类型的container都看完之后,再来说它们的实现。
至此,UnkeyedEncodingContainer
约束的内容就说完了。接下来,我们得回到__JSONEncoder
,来看看unkeyedContainer()
这个方法究竟做了什么。
__JSONEncoder
unkeyedContainer
的实现,定义在这里:
public func unkeyedContainer() -> UnkeyedEncodingContainer {
let topContainer: NSMutableArray
if self.canEncodeNewValue {
topContainer = self.storage.pushUnkeyedContainer()
} else {
guard let container =
self.storage.containers.last as? NSMutableArray else {
preconditionFailure(
"Attempt to push new unkeyed encoding container when already previously encoded at this path.")
}
topContainer = container
}
return _JSONUnkeyedEncodingContainer(
referencing: self,
codingPath: self.codingPath,
wrapping: topContainer)
}
简单来说,就是从self.storage
中获得一个NSMutableArray
,然后用这个数组、__JSONENcoder
对象自身以及codingPath
,创建了一个_JSONUnkeyedEncodingContainer
对象。
_JSONUnkeyedEncodingContainer
而这个_JSONUnkeyedEncodingContainer
,就是我们要找的那个遵从了UnkeyedEncodingContainer
的类型。它的定义在这里。结合刚才看到的创建方法,我们来看下它的属性:
fileprivate struct _JSONUnkeyedEncodingContainer : UnkeyedEncodingContainer {
private let encoder: __JSONEncoder
private let container: NSMutableArray
private(set) public var codingPath: [CodingKey]
public var count: Int {
return self.container.count
}
fileprivate init(
referencing encoder: __JSONEncoder,
codingPath: [CodingKey],
wrapping container: NSMutableArray) {
self.encoder = encoder
self.codingPath = codingPath
self.container = container
}
}
看到了吧,encoder
是我们注入的__JSONEncoder
对象,container
是一个空的NSMutableArray
,codingPath
是__JSONEncoder
对象中的codingPath
。
接下来,就是UnkeyedEncodingContainer
中约束的那一堆encode
方法了,我们从中找一些具有代表性的来看看:
public mutating func encode(_ value: Bool) throws {
self.container.add(self.encoder.box(value))
}
public mutating func encode(_ value: Double) throws {
self.encoder.codingPath.append(_JSONKey(index: self.count))
defer { self.encoder.codingPath.removeLast() }
self.container.add(try self.encoder.box(value))
}
public mutating func encode<T : Encodable>(_ value: T) throws {
self.encoder.codingPath.append(_JSONKey(index: self.count))
defer { self.encoder.codingPath.removeLast() }
self.container.add(try self.encoder.box(value))
}
而选择的依据,就是:
- 编码不会抛出异常的内建类型;
- 编码可能抛出异常的内建类型;
- 编码任意一个遵从了
Encodable
的类型;
聊聊codingPath
其实,编码数据的核心逻辑和SingleValueEncodingContainer
是一样的,都是把值打包之后,放到容器里。只不过,当打包的值可能抛出异常的时候,存入容器之前,也把对应值的Key存到了codingPath
里。但SingleValueEncodingContainer
中却没有这样做,我们对比下其编码Double
的代码就会看到了:
public func encode(_ value: Double) throws {
assertCanEncodeNewValue()
try self.storage.push(container: self.box(value))
}
这是为什么呢?要说在这两种容器里打包数据的唯一区别,就是SingleValueEncodingContainer
的打包结果,是直接放在__JSONEncoder.storage
中的,而UnkeyedEncodingContainer
,则是在storage
中新开辟了一个NSMutableArray
,并把打包的结果放在了这里。把它们的区别用一张图表示,就是这样的:
当我们使用SingleValueEncodingContainer
的时候,一来,只能存储一个值;二来,这个值会直接存储在__JSONENcoder.storage
里,因此,我们完全不需要额外记录这个值在storage中的位置。
但UnkeyedEncodingContainer
就不同了。我们可以在这个container里放两类东西:
- 一类是打包进来的值;
- 另一类是新创建的嵌套容器;
无论是那种情况,如果其中某一个值打包的时候可能发生异常,为了可以在box
方法里获得这个位置,我们就得通过__JSONEncoder.codingPath
不断的跟踪当前正在打包的值在容器中的位置。这就是为什么这两个容器中,对Double
编码处理方式不同的原因了。