我們來研究Swift中對象解碼的實現(xiàn)過程狭莱。有了前面編碼的內(nèi)容做鋪墊,形式上默怨,解碼過程基本就是編碼的“逆過程”骤素。這個過程中用到的類型济竹,數(shù)據(jù)結(jié)構(gòu)和編碼過程是一一對應(yīng)的。因此梦谜,我們就不再像之前研究編碼一樣去逐個討論這些類型的細節(jié)袭景,而是順著解碼的執(zhí)行過程耸棒,來過一遍這部分的實現(xiàn)方式。
JSONDecoder
同樣单山,我們還是從和用戶直接交互的API說起。和JSONEncoder
一樣昼接,JSONDecoder
同樣只是一個包裝類辩棒,它定義在這里:
@_objcRuntimeName(_TtC10Foundation13__JSONDecoder)
open class JSONDecoder {
}
在這個類的一開始膨疏,是和JSONEncoder
對應(yīng)的解碼配置:
open class JSONDecoder {
/// The strategy to use in decoding dates. Defaults to `.deferredToDate`.
open var dateDecodingStrategy: DateDecodingStrategy = .deferredToDate
/// The strategy to use in decoding binary data. Defaults to `.base64`.
open var dataDecodingStrategy: DataDecodingStrategy = .base64
/// The strategy to use in decoding non-conforming numbers. Defaults to `.throw`.
open var nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy = .throw
/// The strategy to use for decoding keys. Defaults to `.useDefaultKeys`.
open var keyDecodingStrategy: KeyDecodingStrategy = .useDefaultKeys
/// Contextual user-provided information for use during decoding.
open var userInfo: [CodingUserInfoKey : Any] = [:]
}
這些配置的含義佃却,和JSONEncoder
中的屬性是完全一樣的饲帅。也就是說,如果在JSONEncoder
中定義了它們育八,在解碼的時候赦邻,也要對JSONDecoder
做同樣的配置惶洲。
接下來,是一個用于包含默認配置的內(nèi)嵌類型和屬性:
open class JSONDecoder {
fileprivate struct _Options {
let dateDecodingStrategy: DateDecodingStrategy
let dataDecodingStrategy: DataDecodingStrategy
let nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy
let keyDecodingStrategy: KeyDecodingStrategy
let userInfo: [CodingUserInfoKey : Any]
}
/// The options set on the top-level decoder.
fileprivate var options: _Options {
return _Options(dateDecodingStrategy: dateDecodingStrategy,
dataDecodingStrategy: dataDecodingStrategy,
nonConformingFloatDecodingStrategy: nonConformingFloatDecodingStrategy,
keyDecodingStrategy: keyDecodingStrategy,
userInfo: userInfo)
}
}
大家知道有這么個東西就行了,它的作用和JSONEncoder
是一樣的铐料。然后钠惩,是JSONDecoder
的默認構(gòu)造函數(shù):
open class JSONDecoder {
// MARK: - Constructing a JSON Decoder
/// Initializes `self` with default strategies.
public init() {}
}
這部分很簡單,沒什么好說的扛拨。最后举塔,就是公開給用戶的decode
方法了:
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
let topLevel: Any
do {
topLevel = try JSONSerialization.jsonObject(with: data)
} catch {
throw DecodingError.dataCorrupted(
DecodingError.Context(codingPath: [],
debugDescription: "The given data was not valid JSON.", underlyingError: error))
}
let decoder = __JSONDecoder(referencing: topLevel, options: self.options)
guard let value = try decoder.unbox(topLevel, as: type) else {
throw DecodingError.valueNotFound(type,
DecodingError.Context(codingPath: [],
debugDescription:
"The given data did not contain a top-level value."))
}
return value
}
除了各種不正常情況拋出的異常之外央渣,decode
的執(zhí)行流程是這樣的:首先芽丹,用JSONSerialization.jsonObject
把傳遞的Data
變成一個Any
對象;其次咕村,用得到的Any
對象和解碼配置創(chuàng)建一個__JSONDecoder
對象懈涛;最后泳猬,用這個__JSONDecoder
對象的unbox
方法,把Any
對象“開箱”變成具體的類型埋心。按照之前的經(jīng)驗不難想象拷呆,這個__JSONDecoder
應(yīng)該是一個遵從了Decoder
的類型晨横。事實上也的確如此手形,它的定義在這里:
fileprivate class __JSONDecoder : Decoder {}
于是,接下來的探索就分成了兩條路伙狐,一條是沿著unbox
方法去看它是如何“開箱”Swift內(nèi)置類型以及任意遵從了Decodable
的類型贷屎;另一條艘虎,則是沿著__JSONDecoder
的Decoder
身份去看它是如何為自定義“開箱”提供支持野建。
本著和研究__JSONEncoder
的順序一致恬叹,我們就先走unbox
這條路同眯。
unbox
根據(jù)之前的經(jīng)驗须蜗,__JSONDecoder
自身應(yīng)該有一大套用于解碼Swift內(nèi)建類型的unbox
方法明肮。當然,實際也是如此大莫,這些方法定義在這里官份。同樣舅巷,我們找一些有代表性的來看看。
由于在編碼的時候赋元,Bool
搁凸、各種形式的Int / UInt
以及浮點數(shù)都編碼成了NSNumber
狠毯。在解碼的時候嚼松,要根據(jù)NSNumber
的值,把原始的數(shù)據(jù)還原回來寝受。因此罕偎,我們分別來看下Bool / Int / Double
這三種類型的“開箱”過程。
首先忙干,是Bool
的開箱浪藻,它的定義在這里:
fileprivate func unbox(_ value: Any, as type: Bool.Type) throws -> Bool? {
guard !(value is NSNull) else { return nil }
if let number = value as? NSNumber {
// TODO: Add a flag to coerce non-boolean numbers into Bools?
if number === kCFBooleanTrue as NSNumber {
return true
} else if number === kCFBooleanFalse as NSNumber {
return false
}
/* FIXME: If swift-corelibs-foundation doesn't change to use NSNumber, this code path will need to be included and tested:
} else if let bool = value as? Bool {
return bool
*/
}
throw DecodingError._typeMismatch(
at: self.codingPath, expectation: type, reality: value)
}
可以看到爱葵,如果value
是NSNull
或者value
不能轉(zhuǎn)型成NSNumber
萌丈,都會進行對應(yīng)的錯誤處理雷则,不過這部分我們就忽略了月劈。如果value
是一個NSNumber
,那么就根據(jù)它的值是kCFBooleanTrue / kCFBooleanFalse
惭墓,返回Swift對應(yīng)的true / false
腊凶。這樣拴念,就把從Foundation得到的Any
對象轉(zhuǎn)型成了Bool
政鼠。
其次,是Int
的開箱弛秋,它的定義在這里:
fileprivate func unbox(_ value: Any, as type: Int.Type) throws -> Int? {
guard !(value is NSNull) else { return nil }
guard let number = value as? NSNumber,
number !== kCFBooleanTrue,
number !== kCFBooleanFalse else {
throw DecodingError._typeMismatch(
at: self.codingPath, expectation: type, reality: value)
}
let int = number.intValue
guard NSNumber(value: int) == number else {
throw DecodingError.dataCorrupted(
DecodingError.Context(
codingPath: self.codingPath,
debugDescription:
"Parsed JSON number <\(number)> does not fit in \(type)."))
}
return int
}
可以看到蟹略,大體的流程和解碼Bool
是類似的挖炬,判斷value
可以成功轉(zhuǎn)型成NSNumber
之后状婶,就把NSNumber.intValue
賦值給了Swift中對應(yīng)類型Int
的變量。完成后钓猬,unbox
還做了一層額外的檢查撩独,也就是確保目標變量int
可以容納下NSNumber
表示的值综膀。否則剧劝,就會生成一個數(shù)據(jù)損壞的異常。無論是Swift中有符號數(shù)或無符號數(shù)拢锹,也無論我們是否指定整數(shù)類型的長度面褐,它們的解碼邏輯是都一樣的取胎,只不過完成賦值之后,檢查數(shù)據(jù)寬度時使用的類型不同而已匪傍,我們就不一一列舉了役衡,大家感興趣的話薪棒,可以自己去看看俐芯。
最后吧史,再來看浮點數(shù)的開箱,它的定義在這里:
fileprivate func unbox(_ value: Any, as type: Double.Type) throws -> Double? {
guard !(value is NSNull) else { return nil }
if let number = value as? NSNumber,
number !== kCFBooleanTrue,
number !== kCFBooleanFalse {
// We are always willing to return the number as a Double:
// * If the original value was integral, it is guaranteed to fit in a Double; we are willing to lose precision past 2^53 if you encoded a UInt64 but requested a Double
// * If it was a Float or Double, you will get back the precise value
// * If it was Decimal, you will get back the nearest approximation
return number.doubleValue
} else if let string = value as? String,
case .convertFromString(
let posInfString,
let negInfString,
let nanString) = self.options.nonConformingFloatDecodingStrategy {
if string == posInfString {
return Double.infinity
} else if string == negInfString {
return -Double.infinity
} else if string == nanString {
return Double.nan
}
}
throw DecodingError._typeMismatch(
at: self.codingPath,
expectation: type, reality: value)
}
很明顯岩睁,這要比開箱Bool
和Int
復(fù)雜多了揣云。原因有兩個:一個是這段代碼前半段注釋中說明的有可能編碼的時候是整數(shù),但卻按照Double
開箱邓夕。這時刘莹,可以分成三種情況:
- 首先,是編碼一個
UInt64
對象翎迁,開箱時超過253的部分會被忽略; - 其次净薛,是編碼一個
Double/Float
對象汪榔,開箱時就就會直接還原成Double
; - 最后肃拜,是編碼一個
Decimal
對象痴腌,會還原成與其最接近的值燃领;
但事情至此還沒完士聪,除了這些合法的浮點數(shù)之外,編碼的時候我們看到過了猛蔽,還可以用字符串定義各種非法的浮點數(shù)呢剥悟。因此,如果編碼的時候采用了這種策略曼库,開箱的時候必須能夠處理区岗,而這就是“開箱”Double
后半部分的代碼。如果value
可以轉(zhuǎn)換成String
毁枯,那就按照JSONDecoder
中關(guān)于解碼浮點數(shù)的配置慈缔,把字符串分別轉(zhuǎn)換成對應(yīng)的infinity / nan
。
至此种玛,這三種內(nèi)建類型的解碼就說完了藐鹤。接下來還有什么呢?沒錯赂韵,編碼的時候娱节,我們還看過Date
和Data
,到了開箱祭示,這兩種類型只是根據(jù)JSONDecoder
傳遞的類型解碼配置括堤,把Any
還原成對應(yīng)的類型罷了。我們來看個開箱Data
的例子,它的定義在這里:
fileprivate func unbox(_ value: Any, as type: Data.Type) throws -> Data? {
guard !(value is NSNull) else { return nil }
switch self.options.dataDecodingStrategy {
case .deferredToData:
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try Data(from: self)
case .base64:
guard let string = value as? String else {
throw DecodingError._typeMismatch(
at: self.codingPath, expectation: type, reality: value)
}
guard let data = Data(base64Encoded: string) else {
throw DecodingError.dataCorrupted(
DecodingError.Context(
codingPath: self.codingPath,
debugDescription: "Encountered Data is not valid Base64."))
}
return data
case .custom(let closure):
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try closure(self)
}
}
看到了吧悄窃,其實關(guān)鍵點就是case
語句中的幾個return
讥电,要原始數(shù)據(jù)就是原始數(shù)據(jù),要base64編碼就base64編碼轧抗,要執(zhí)行定義過程就執(zhí)行自定義過程恩敌,之后,把生成的Data
返回就是了横媚。至于解碼Date
的思路纠炮,和Data
時類似的,只是操作的數(shù)據(jù)不同灯蝴,大家可以自己去看代碼恢口,我們就不重復(fù)了。
看完了這些unbox
方法之后穷躁,不難推測耕肩,在一開始__JSONDecoder
里調(diào)用的unbox
應(yīng)該就是一個“開箱”的入口函數(shù),它只負責(zé)把開箱工作轉(zhuǎn)發(fā)給各種負責(zé)具體類型的unbox
函數(shù)里问潭。事實上的確如此猿诸,它的定義在這里:
fileprivate func unbox<T : Decodable>(
_ value: Any, as type: T.Type) throws -> T? {
return try unbox_(value, as: type) as? T
}
而這個unbox_
就是最終派發(fā)工作的人,它的定義在這里:
fileprivate func unbox_(_ value: Any, as type: Decodable.Type) throws -> Any? {
if type == Date.self || type == NSDate.self {
return try self.unbox(value, as: Date.self)
} else if type == Data.self || type == NSData.self {
return try self.unbox(value, as: Data.self)
} else if type == URL.self || type == NSURL.self {
guard let urlString = try self.unbox(value, as: String.self) else {
return nil
}
guard let url = URL(string: urlString) else {
throw DecodingError.dataCorrupted(
DecodingError.Context(
codingPath: self.codingPath,
debugDescription: "Invalid URL string."))
}
return url
} else if type == Decimal.self || type == NSDecimalNumber.self {
return try self.unbox(value, as: Decimal.self)
} else if let stringKeyedDictType =
type as? _JSONStringDictionaryDecodableMarker.Type {
return try self.unbox(value, as: stringKeyedDictType)
} else {
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try type.init(from: self)
}
}
看著挺長狡忙,實際上梳虽,只要你跟住每一個if
里的return
就不難理解它的作用了。