該如何研究Swift中的Codable系統(tǒng)呢?從最粗的線條來說,我們的路徑分成兩條:
- 一條是從應(yīng)用代碼追到標準庫的實現(xiàn);
- 一條是從向用戶開放的公開類型追到系統(tǒng)自身使用的內(nèi)部類型;
其中,前者可以幫助我們理解整個系統(tǒng)的工作流程,后者可以幫助我們探索實現(xiàn)流程的種種細節(jié)。作為整個系列的開始,這一節(jié),我們先來理解編碼數(shù)據(jù)的整體流程。
從Codable說起
和編碼/解碼數(shù)據(jù)相關(guān)的主要代碼文件,有兩個,分別是:
-
這個gyb模板里,定義了對用戶公開的相關(guān)
protocol。以及Swift內(nèi)置類型對Codable的實現(xiàn); -
JSONEncoder.swift。顧名思義,這就是Swift中
JSONEncoder和JSONDecoder的實現(xiàn)。在這個系列里,我們就用這兩個類型作為代表來研究對象的編碼和解碼過程了;
接下來,我們就從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
}
看到這,我們不難繼續(xù)聯(lián)想出三個問題:
- 首先,那Swift內(nèi)置的所有支持
Encodable的類型都實現(xiàn)了這個方法么? - 其次,Swift中究竟有哪些默認支持
Encodable的方法呢? - 最后,
Encoder又是什么?
第一個問題的答案當然是肯定的,并且這些默認類型的實現(xiàn),也都在一開始我們提到的Codable.swift.gyb這個模板文件里。例如,
Int的默認實現(xiàn)實現(xiàn)是這樣的:
extension Int : Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self)
}
}
Array的默認實現(xiàn)是這樣的:
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)
}
}
}
當然,現(xiàn)在的重點并不是這些實現(xiàn)的細節(jié),而是感性地知道:“喔,原來Swift真的給每一個內(nèi)建的Encodable類型都實現(xiàn)了對應(yīng)的方法”這件事情就好了。
那么,在Swift里,一共有多少個支持Encodable的內(nèi)建類型呢?這個問題的答案同樣在Codable.swift.gyb文件里。在這個模板文件的一開始,就可以看到這樣一段代碼:
%{
codable_types = ['Bool', 'String', 'Double', 'Float',
'Int', 'Int8', 'Int16', 'Int32', 'Int64',
'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64']
}%
Python會提取這個數(shù)組中的每一個類型,為其生成對應(yīng)的編碼方法。當然,除了這些之外,還有Array / Set / Dictionary / Optional等類型,大家在Codable.swift.gyb文件的底部,可以找到這些實現(xiàn)。
說到這,就剩下最后一個問題了,encode方法中的Encoder又是什么呢?如果把它當成一個黑盒子看,這里就是“魔法發(fā)生的地方”,它最終把Swift對象編碼成JSON字符串。
JSONEncoder
但要搞清楚這里面究竟發(fā)生了什么,我們就得從JSONEncoder,這個直接和用戶打交道的編碼類型說起了。JSONEncoder的定義在這里,實際上它只是一個面向用戶的包裝類。在它的一開始,定義了一些內(nèi)部類型,這些類型用于配置編碼的行為:
- JSON編碼結(jié)果的輸出格式(
public struct OutputFormatting); -
Date類型的編碼方式(public enum DateEncodingStrategy); -
Data類型的編碼方式(public enum DataEncodingStrategy); - 不合法浮點數(shù)的編碼方式(
public enum NonConformingFloatEncodingStrategy); - JSON中key的編碼方式(
public enum KeyEncodingStrategy);
當然,我們現(xiàn)在的重點并不是這些類型的實現(xiàn)細節(jié),只要知道它們各自的作用就好了。如果你之前用過JSONEncoder,應(yīng)該對這些類型也并不陌生。
接下來,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還定義了一個內(nèi)部類型_Options和內(nèi)部屬性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中的默認配置部分就說完了,接下來,是我們用于創(chuàng)建JSONEncoder對象的默認構(gòu)造函數(shù),這也是JSONEncoder唯一的一個構(gòu)造函數(shù):
open class JSONEncoder {
public init() {}
}
最后,則是我們使用的encode方法,它的聲明是這樣的:
open func encode<T : Encodable>(_ value: T) throws -> Data
這個函數(shù)在整個編碼過程中,是一個重要的分水嶺。一方面,它是整個編碼系統(tǒng)面向用戶的最后一道關(guān)卡,順著它再往下追,就是編碼過程的內(nèi)部實現(xiàn)細節(jié)了;另一方面,它也是我們了解編碼系統(tǒng)內(nèi)部工作的第一道大門,是探索之前看到的protocol Encoder的開始。
__JSONEncoder
那么,接下來,我們就順著JSONEncoder.encode方法的實現(xiàn)開始吧,這個函數(shù)定義在這里。我們先來看下它的執(zhí)行邏輯:
open func encode<T : Encodable>(_ 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
}
}
在上面的代碼里,我用///注釋替代了代碼中和主要執(zhí)行邏輯無關(guān)的細節(jié),當前我們的重點,還是在這個方法的執(zhí)行邏輯上。在encode的實現(xiàn)里:
- 首先,創(chuàng)建了一個
__JSONEncoder對象,它才是遵從了Encoder的類型; - 其次,
__JSONEncoder有一個box_方法,從表面看,它的功能,就是把參數(shù)value“打包”成一個可以進行JSON編碼的數(shù)據(jù); - 最后,在經(jīng)過了一系列錯誤檢查和準備之后,它調(diào)用了Foundation中的
JSONSerialization.data完成了數(shù)據(jù)的最終編碼,并返回一個包含編碼結(jié)果的Data對象;
把我們現(xiàn)在整理出來的內(nèi)容用一張圖表示,就是這樣的:
