__JSONEncoder是一個(gè)遵從了Encoder的類(lèi)型,從JSONEncoder.encode的實(shí)現(xiàn)看,它才是真正開(kāi)始處理數(shù)據(jù)編碼的類(lèi)型。并且,在Encodable約束的方法中,也需要傳入一個(gè)遵從Encoder類(lèi)型的參數(shù)。那么,這個(gè)Encoder究竟做了什么呢?為了搞清楚這個(gè)問(wèn)題,我們只能追著__JSONEncoder的實(shí)現(xiàn)去一探究竟了。
__JSONEncoder
首先,是它的屬性和初始化方法:
fileprivate class __JSONEncoder : Encoder {
fileprivate var storage: _JSONEncodingStorage
fileprivate let options: JSONEncoder._Options
public var codingPath: [CodingKey]
public var userInfo: [CodingUserInfoKey : Any] {
return self.options.userInfo
}
fileprivate init(
options: JSONEncoder._Options,
codingPath: [CodingKey] = []) {
self.options = options
self.storage = _JSONEncodingStorage()
self.codingPath = codingPath
}
}
在上面的代碼里,codingPath和userInfo對(duì)于理解編碼的整體流程來(lái)說(shuō)并不重要,因此接下來(lái)的代碼分析里,我們先暫時(shí)忽略它們。options是一個(gè)JSONEncoder._Options對(duì)象,通過(guò)上一節(jié)我們知道,這就是JSON編碼時(shí)使用的各種配置的集合。結(jié)合上一節(jié)看到的創(chuàng)建__JSONEncoder的代碼:
let encoder = __JSONEncoder(options: self.options)
就看的更清楚了,這里傳遞的就是JSONEncoder中表示配置集合的options屬性。
_JSONEncodingStorage
那么剩下的問(wèn)題就變成了_JSONEncodingStorage是什么呢?它的定義在這里。其實(shí),就像這個(gè)類(lèi)型的名字一樣,它就是__JSONEncoding這個(gè)類(lèi)型使用的存儲(chǔ)空間:
fileprivate struct _JSONEncodingStorage {
// MARK: Properties
/// The container stack.
/// Elements may be any one of the JSON types
/// (NSNull, NSNumber, NSString, NSArray, NSDictionary).
private(set) fileprivate var containers: [NSObject] = []
fileprivate init() {}
}
上面的注釋中說(shuō)了,在這個(gè)存儲(chǔ)中保存的,只能是NSNull / NSNumber / NSString / NSArray / NSDictionary這些類(lèi)型。至于為什么,我們一會(huì)兒往后看自然就明白了。
另外,_JSONEncodingStorage還有一些向container中添加刪除元素的方法:
fileprivate mutating func pushKeyedContainer() -> NSMutableDictionary {
let dictionary = NSMutableDictionary()
self.containers.append(dictionary)
return dictionary
}
fileprivate mutating func pushUnkeyedContainer() -> NSMutableArray {
let array = NSMutableArray()
self.containers.append(array)
return array
}
fileprivate mutating func push(container: __owned NSObject) {
self.containers.append(container)
}
fileprivate mutating func popContainer() -> NSObject {
precondition(!self.containers.isEmpty, "Empty container stack.")
return self.containers.popLast()!
}
用一句話(huà)總結(jié)這些方法就是,我們可以向在隊(duì)尾追加NSMutableDictionary / NSMutableArray / NSObject對(duì)象,或者刪除隊(duì)尾的元素。最后,_JSONEncodingStorage還有一個(gè)獲取存儲(chǔ)大小的計(jì)算屬性:
fileprivate var count: Int {
return self.containers.count
}
以上這就是這個(gè)內(nèi)部存儲(chǔ)類(lèi)型的全部了。
向storage中添加元素
不過(guò),看到這你可能會(huì)想了,作為__JSONEncoder的內(nèi)部存儲(chǔ),如果只能存儲(chǔ)之前提到的那些NS開(kāi)頭的對(duì)象,Swift里的類(lèi)型顯然不止這些啊,這是怎么回事呢?實(shí)際上,__JSONEncoder有一個(gè)擴(kuò)展,它可以把Swift中的各種內(nèi)置類(lèi)型,變成適合存放在這個(gè)內(nèi)部存儲(chǔ)中的類(lèi)型。這個(gè)擴(kuò)展的定義在這里。
打包整數(shù)
先來(lái)看幾個(gè)簡(jiǎn)單的:
extension __JSONEncoder {
fileprivate func box(_ value: Bool) -> NSObject {
return NSNumber(value: value)
}
fileprivate func box(_ value: Int) -> NSObject {
return NSNumber(value: value)
}
fileprivate func box(_ value: String) -> NSObject {
return NSString(string: value)
}
}
看到了吧,通過(guò)這些重載的box方法,Bool/Int統(tǒng)一被“打包”成NSNumber,String被“打包”成NSString。實(shí)際上,Swift中的每一個(gè)整數(shù)類(lèi)型,都有一個(gè)對(duì)應(yīng)的box方法,把它“打包”成一個(gè)NSNumber對(duì)象。
打包浮點(diǎn)數(shù)
再來(lái)看個(gè)稍微復(fù)雜一點(diǎn)兒的:
fileprivate func box(_ double: Double) throws -> NSObject {
guard !double.isInfinite && !double.isNaN else {
guard case let .convertToString(
positiveInfinity: posInfString,
negativeInfinity: negInfString,
nan: nanString) = self.options.nonConformingFloatEncodingStrategy else {
throw EncodingError._invalidFloatingPointValue(double, at: codingPath)
}
if double == Double.infinity {
return NSString(string: posInfString)
} else if double == -Double.infinity {
return NSString(string: negInfString)
} else {
return NSString(string: nanString)
}
}
return NSNumber(value: double)
}
當(dāng)然,如果一切正常,Swift中的Double也會(huì)被打包成NSNumber。但由于Double中有infinity和nan這兩種情況:
let nan: Double = Double.nan
let infinity = Double.infinity
顯然,編碼過(guò)程不能對(duì)其置之不理,于是就有了返回NSNumber之前這一大坨代碼。上一節(jié)我們說(shuō)過(guò),JSONEncoder中有一個(gè)控制浮點(diǎn)數(shù)編碼的配置:
public enum NonConformingFloatEncodingStrategy {
case `throw`
case convertToString(
positiveInfinity: String,
negativeInfinity: String,
nan: String)
}
這個(gè)配置的默認(rèn)值是case throw,因此遇到這些特殊浮點(diǎn)數(shù)的時(shí)候,box方法就會(huì)拋出EncodingError._invalidFloatingPointValue異常。但如果我們想自定義這些特殊浮點(diǎn)數(shù)編碼,只要像下面這樣改變處理浮點(diǎn)數(shù)的配置就好了:
let encoder = JSONEncoder()
encoder.nonConformingFloatEncodingStrategy =
.convertToString(positiveInfinity: "Infinity",
negativeInfinity: "Negtive infinity",
nan: "Not a number")
然后,當(dāng)我們編碼下面這個(gè)數(shù)組的時(shí)候:
let doubleData = try encoder.encode([
Double.infinity,
-Double.infinity,
Double.nan
])
最終,box方法就會(huì)把它們變成我們指定的字符串并返回了。這就是打包Double類(lèi)型的過(guò)程(如果你去翻一下源代碼就會(huì)發(fā)現(xiàn),Float的處理邏輯和Double是完全一樣的)。
打包Dictionary
我們剛才看過(guò)的這兩類(lèi)box方法,都直接對(duì)傳遞給它的參數(shù)進(jìn)行了打包。接下來(lái),我們?cè)賮?lái)看一類(lèi)需要借助_JSONEncodingStorage進(jìn)行打包的box方法。這里,就用打包Dictionary類(lèi)型舉例,它的定義在這里:
fileprivate func box(_ dict: [String : Encodable]) throws -> NSObject? {
/// 1\. Request a new container
let depth = self.storage.count
let result = self.storage.pushKeyedContainer()
/// 2\. Box data
do {
for (key, value) in dict {
self.codingPath.append(
_JSONKey(stringValue: key, intValue: nil))
defer { self.codingPath.removeLast() }
result[key] = try box(value)
}
} catch {
if self.storage.count > depth {
let _ = self.storage.popContainer()
}
throw error
}
// 3\. Return the result
guard self.storage.count > depth else {
return nil
}
return self.storage.popContainer()
}
這個(gè)方法里要說(shuō)的東西還蠻多的,為了方便理解,我在它的實(shí)現(xiàn)里添加了注釋?zhuān)瑏?lái)區(qū)分它執(zhí)行過(guò)程中的三個(gè)部分。第一部分,是從storage中開(kāi)辟一個(gè)新空間,通過(guò)之前的實(shí)現(xiàn)就知道,result現(xiàn)在是一個(gè)空的NSMutableDictionary對(duì)象。
第二部分,是在result中存儲(chǔ)dict中的內(nèi)容。在這個(gè)遍歷dict的循環(huán)中,_JSONKey(stringValue: key, intValue: nil)從字面上說(shuō),就表示執(zhí)行JSON編碼時(shí),某一項(xiàng)使用的key。至于它具體的實(shí)現(xiàn),我們先放放,理解它的含義就好了。之所以要在codingPath中保存這個(gè)值,是因?yàn)榻酉聛?lái),使用try box(value)打包對(duì)應(yīng)value時(shí),這是一個(gè)可能發(fā)生異常的方法,而這個(gè)異常,有可能會(huì)用到當(dāng)前的這個(gè)key的信息,以幫助開(kāi)發(fā)者更好的排錯(cuò)。例如,在我們剛剛看到過(guò)的打包Double的方法里:
fileprivate func box(_ double: Double) throws -> NSObject {
guard !double.isInfinite && !double.isNaN else {
guard case let .convertToString(
positiveInfinity: posInfString,
negativeInfinity: negInfString,
nan: nanString) = self.options.nonConformingFloatEncodingStrategy else {
throw EncodingError._invalidFloatingPointValue(double, at: codingPath)
}
/// ...
}
想象一下dict中,某個(gè)key對(duì)應(yīng)的value是一個(gè)不合法的浮點(diǎn)數(shù),拋出的異常中,就會(huì)同時(shí)帶有key和value的信息了。
當(dāng)然,如果對(duì)應(yīng)的value成功打包了,就直接把它保存到result[key]中就好了。無(wú)論這個(gè)打包的結(jié)果如何,每一次循環(huán)之后,這個(gè)_JSONKey創(chuàng)建的索引就沒(méi)用了,在開(kāi)始下一次迭代之前,使用defer { self.codingPath.removeLast() }確保了無(wú)論在任何情況下,都可以把剛插入的這個(gè)_JSONKey值撤銷(xiāo)掉。
另外,如果遍歷dict的過(guò)程中發(fā)生了異常,編碼過(guò)程肯定就無(wú)法繼續(xù)下去了,于是在catch語(yǔ)句里,我們先把之前從storage中申請(qǐng)到的空間清理掉,然后重新拋出發(fā)生的異常。
以上,就是打包dict值的過(guò)程。
第三部分,是返回打包結(jié)果。如果可以成功執(zhí)行到這里,應(yīng)該可以說(shuō),dict的值是成功打包完了的,這時(shí),我們只要返回storage中的最后一項(xiàng),自然就是包含打包結(jié)果的NSMutableDictionary對(duì)象了。
至于return語(yǔ)句上面的guard,多多少少應(yīng)該有點(diǎn)兒防御性代碼的味道。什么時(shí)候self.storage.count會(huì)小于等于depth呢?唯一的解釋?zhuān)仓荒芫褪窃谝婚_(kāi)始從storage開(kāi)辟空間的時(shí)候,就失敗了,以至于整個(gè)編碼過(guò)程都沒(méi)執(zhí)行才會(huì)如此,emm... 想必這種情況,是很少見(jiàn)的。但無(wú)論如何,只要這種情況發(fā)生了,就直接返回nil。
這樣,打包Dictionary的過(guò)程就全部說(shuō)完了。
通用的box_
希望你還記得,上一節(jié)說(shuō)到encode方法實(shí)現(xiàn)的時(shí)候,在創(chuàng)建完__JSONEncoder對(duì)象之后,調(diào)用了一個(gè)叫做box_的方法。在理解了所有打包具象類(lèi)型的box方法之后,現(xiàn)在是時(shí)候來(lái)看它的實(shí)現(xiàn)了。
簡(jiǎn)單來(lái)說(shuō),它就是一個(gè)打包數(shù)據(jù)方法的匯總,在它的前半段實(shí)現(xiàn)中,判斷了要打包的數(shù)據(jù)類(lèi)型,如果是之前實(shí)現(xiàn)過(guò)的有內(nèi)建打包代碼的類(lèi)型,就調(diào)用對(duì)應(yīng)的box方法:
fileprivate func box_(_ value: Encodable) throws -> NSObject? {
let type = Swift.type(of: value)
if type == Date.self || type == NSDate.self {
// Respect Date encoding strategy
return try self.box((value as! Date))
} else if type == Data.self || type == NSData.self {
// Respect Data encoding strategy
return try self.box((value as! Data))
} else if type == URL.self || type == NSURL.self {
// Encode URLs as single strings.
return self.box((value as! URL).absoluteString)
} else if type == Decimal.self || type == NSDecimalNumber.self {
// JSONSerialization can natively handle NSDecimalNumber.
return (value as! NSDecimalNumber)
} else if value is _JSONStringDictionaryEncodableMarker {
return try self.box(value as! [String : Encodable])
}
}
否則,如果沒(méi)有匹配,則說(shuō)明value僅僅是一個(gè)支持Encodable的類(lèi)型。該如何打包這個(gè)數(shù)據(jù),則完全依賴(lài)這個(gè)類(lèi)型自身提供的encode(to:)方法了。這部分的實(shí)現(xiàn)和打包Dictionary時(shí)的邏輯,是非常類(lèi)似的:
fileprivate func box_(_ value: Encodable) throws -> NSObject? {
/// The same as before...
let depth = self.storage.count
do {
try value.encode(to: self)
} catch {
if self.storage.count > depth {
let _ = self.storage.popContainer()
}
throw error
}
// The top container should be a new container.
guard self.storage.count > depth else {
return nil
}
return self.storage.popContainer()
}
看到了吧,核心邏輯就是調(diào)用try value.encode(to: self)編碼value,得到結(jié)果應(yīng)該保存在storage中的最后一個(gè)元素里。完成后,把這個(gè)元素返回,就是這個(gè)遵從了Encodable類(lèi)型的值的打包結(jié)果了。
看到這你可能會(huì)想了,哎~~~~等會(huì)等會(huì),為什么編碼的結(jié)果就保存在storage的最后一個(gè)元素里呢?emm...要說(shuō)清這個(gè)過(guò)程我們還有一段路要走,現(xiàn)在姑且就這么理解就行了。
因此,只要執(zhí)行到這里,我們就應(yīng)該相信,box_的返回值,就是一個(gè)被包裝好的了可以扔給Foundation API去執(zhí)行JSON編碼的值了。這時(shí),我們不妨把上一節(jié)中的encode代碼在羅列出來(lái)看一下,之前省略掉了一些錯(cuò)誤處理的邏輯,現(xiàn)在理解了box_之后,就可以把它們呈現(xiàn)出來(lái)了:
open func encode<T : Encodable>(_ value: T) throws -> Data {
let encoder = __JSONEncoder(options: self.options)
guard let topLevel = try encoder.box_(value) else {
throw EncodingError.invalidValue(
value,
EncodingError.Context(codingPath: [],
debugDescription:
"Top-level \(T.self) did not encode any values."))
}
if topLevel is NSNull {
throw EncodingError.invalidValue(
value,
EncodingError.Context(codingPath: [],
debugDescription:
"Top-level \(T.self) encoded as null JSON fragment."))
} else if topLevel is NSNumber {
throw EncodingError.invalidValue(
value,
EncodingError.Context(codingPath: [],
debugDescription:
"Top-level \(T.self) encoded as number JSON fragment."))
} else if topLevel is NSString {
throw EncodingError.invalidValue(
value,
EncodingError.Context(codingPath: [],
debugDescription:
"Top-level \(T.self) encoded as string JSON fragment."))
}
/// The same as before...
}
雖然看著挺長(zhǎng),不過(guò)它表達(dá)的邏輯很簡(jiǎn)單,執(zhí)行過(guò)box_之后,只有最外層的類(lèi)型是NSMutableArray或者NSMutableDictionary的時(shí)候,這個(gè)結(jié)果才能送往Foundation進(jìn)行JSON編碼。如果打包出來(lái)的類(lèi)型只是某個(gè)單一形式的值,就會(huì)拋出對(duì)應(yīng)的異常。例如,如果你執(zhí)行下面的代碼:
let data = try JSONEncoder().encode(11)
就會(huì)在控制臺(tái)看到"Top-level Int encoded as number JSON fragment."這樣的錯(cuò)誤提示了。