该如何研究Swift中的Codable系统呢?从最粗的线条来说,我们的路径分成两条:
- 一条是从应用代码追到标准库的实现;
- 一条是从向用户开放的公开类型追到系统自身使用的内部类型;
其中,前者可以帮助我们理解整个系统的工作流程,后者可以帮助我们探索实现流程的种种细节。作为整个系列的开始,这一节,我们先来理解编码数据的整体流程。
从Codable说起
和编码/解码数据相关的主要代码文件,有两个,分别是:
- 这个gyb模板里,定义了对用户公开的相关
protocol
。以及Swift内置类型对Codable
的实现; - JSONEncoder.swift。顾名思义,这就是Swift中
JSONEncoder
和JSONDecoder
的实现。在这个系列里,我们就用这两个类型作为代表来研究对象的编码和解码过程了;
接下来,我们就从Codable
说起,它的定义在这里:
public typealias Codable = Encodable & Decodable
看到了吧,实际上它只是一个别名而已。而Encodable
和Decodable
则是两个protocol
,它们分别约束了一个“可以被编码的类型”和“一个可以被解码的类型”需要支持的操作。
Encodable
那么,究竟什么才是一个Encodable
的类型呢?其实,Swift对它的要求,仅仅是提供一个叫做encode(to:)
方法就好了:
public protocol Encodable {
/// Encodes this value into the given encoder.
///
/// If the value fails to encode anything, `encoder` will encode an empty
/// keyed container in its place.
///
/// This function throws an error if any values are invalid for the given
/// encoder's format.
///
/// - Parameter encoder: The encoder to write data to.
func encode(to encoder: Encoder) throws
}
看到这,我们不难继续联想出三个问题:
- 首先,那Swift内置的所有支持
Encodable
的类型都实现了这个方法么? - 其次,Swift中究竟有哪些默认支持
Encodable
的方法呢? - 最后,
Encoder
又是什么?
第一个问题的答案当然是肯定的,并且这些默认类型的实现,也都在一开始我们提到的Codable.swift.gyb
这个模板文件里。例如,
Int
的默认实现实现是这样的:
extension Int : Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self)
}
}
Array
的默认实现是这样的:
extension Array : Encodable where Element : Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
for element in self {
try container.encode(element)
}
}
}
当然,现在的重点并不是这些实现的细节,而是感性地知道:“喔,原来Swift真的给每一个内建的Encodable
类型都实现了对应的方法”这件事情就好了。
那么,在Swift里,一共有多少个支持Encodable
的内建类型呢?这个问题的答案同样在Codable.swift.gyb
文件里。在这个模板文件的一开始,就可以看到这样一段代码:
%{
codable_types = ['Bool', 'String', 'Double', 'Float',
'Int', 'Int8', 'Int16', 'Int32', 'Int64',
'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64']
}%
Python会提取这个数组中的每一个类型,为其生成对应的编码方法。当然,除了这些之外,还有Array / Set / Dictionary / Optional
等类型,大家在Codable.swift.gyb
文件的底部,可以找到这些实现。
说到这,就剩下最后一个问题了,encode
方法中的Encoder
又是什么呢?如果把它当成一个黑盒子看,这里就是“魔法发生的地方”,它最终把Swift对象编码成JSON字符串。
JSONEncoder
但要搞清楚这里面究竟发生了什么,我们就得从JSONEncoder
,这个直接和用户打交道的编码类型说起了。JSONEncoder
的定义在这里,实际上它只是一个面向用户的包装类。在它的一开始,定义了一些内部类型,这些类型用于配置编码的行为:
- JSON编码结果的输出格式(
public struct OutputFormatting
); -
Date
类型的编码方式(public enum DateEncodingStrategy
); -
Data
类型的编码方式(public enum DataEncodingStrategy
); - 不合法浮点数的编码方式(
public enum NonConformingFloatEncodingStrategy
); - JSON中key的编码方式(
public enum KeyEncodingStrategy
);
当然,我们现在的重点并不是这些类型的实现细节,只要知道它们各自的作用就好了。如果你之前用过JSONEncoder
,应该对这些类型也并不陌生。
接下来,JSONEncoder
中包含了上面这些类型的对象作为属性,这些属性,就是使用JSONEncoder
进行编码时,使用的默认配置:
open class JSONEncoder {
open var outputFormatting: OutputFormatting = []
open var dateEncodingStrategy: DateEncodingStrategy = .deferredToDate
open var dataEncodingStrategy: DataEncodingStrategy = .base64
open var nonConformingFloatEncodingStrategy:
NonConformingFloatEncodingStrategy = .throw
open var keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys
}
为了方便使用这些默认配置,JSONEncoder
还定义了一个内部类型_Options
和内部属性options
:
open class JSONEncoder {
fileprivate struct _Options {
let dateEncodingStrategy: DateEncodingStrategy
let dataEncodingStrategy: DataEncodingStrategy
let nonConformingFloatEncodingStrategy:
NonConformingFloatEncodingStrategy
let keyEncodingStrategy: KeyEncodingStrategy
let userInfo: [CodingUserInfoKey : Any]
}
fileprivate var options: _Options {
return _Options(dateEncodingStrategy: dateEncodingStrategy,
dataEncodingStrategy: dataEncodingStrategy,
nonConformingFloatEncodingStrategy: nonConformingFloatEncodingStrategy,
keyEncodingStrategy: keyEncodingStrategy,
userInfo: userInfo)
}
}
至此,JSONEncoder
中的默认配置部分就说完了,接下来,是我们用于创建JSONEncoder
对象的默认构造函数,这也是JSONEncoder
唯一的一个构造函数:
open class JSONEncoder {
public init() {}
}
最后,则是我们使用的encode
方法,它的声明是这样的:
open func encode(_ value: T) throws -> Data
这个函数在整个编码过程中,是一个重要的分水岭。一方面,它是整个编码系统面向用户的最后一道关卡,顺着它再往下追,就是编码过程的内部实现细节了;另一方面,它也是我们了解编码系统内部工作的第一道大门,是探索之前看到的protocol Encoder
的开始。
__JSONEncoder
那么,接下来,我们就顺着JSONEncoder.encode
方法的实现开始吧,这个函数定义在这里。我们先来看下它的执行逻辑:
open func encode(_ value: T) throws -> Data {
let encoder = __JSONEncoder(options: self.options)
guard let topLevel = try encoder.box_(value) else {
/// throw exception
}
/// Handle invalid box value.
let writingOptions =
JSONSerialization.WritingOptions(
rawValue: self.outputFormatting.rawValue)
do {
return try JSONSerialization.data(
withJSONObject: topLevel, options: writingOptions)
} catch {
/// throw exception
}
}
在上面的代码里,我用///
注释替代了代码中和主要执行逻辑无关的细节,当前我们的重点,还是在这个方法的执行逻辑上。在encode
的实现里:
- 首先,创建了一个
__JSONEncoder
对象,它才是遵从了Encoder
的类型; - 其次,
__JSONEncoder
有一个box_
方法,从表面看,它的功能,就是把参数value
“打包”成一个可以进行JSON编码的数据; - 最后,在经过了一系列错误检查和准备之后,它调用了Foundation中的
JSONSerialization.data
完成了数据的最终编码,并返回一个包含编码结果的Data
对象;
把我们现在整理出来的内容用一张图表示,就是这样的: