Swift5 解碼對象的工作流程

我們來研究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的類型贷屎;另一條艘虎,則是沿著__JSONDecoderDecoder身份去看它是如何為自定義“開箱”提供支持野建。

本著和研究__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)
}

可以看到爱葵,如果valueNSNull或者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)
}

很明顯岩睁,這要比開箱BoolInt復(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)建類型的解碼就說完了藐鹤。接下來還有什么呢?沒錯赂韵,編碼的時候娱节,我們還看過DateData,到了開箱祭示,這兩種類型只是根據(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就不難理解它的作用了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末灾茁,一起剝皮案震驚了整個濱河市窜觉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌北专,老刑警劉巖竖螃,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異逗余,居然都是意外死亡特咆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門录粱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腻格,“玉大人,你說我怎么就攤上這事啥繁〔酥埃” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵旗闽,是天一觀的道長酬核。 經(jīng)常有香客問我蜜另,道長,這世上最難降的妖魔是什么嫡意? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任举瑰,我火速辦了婚禮,結(jié)果婚禮上蔬螟,老公的妹妹穿的比我還像新娘此迅。我一直安慰自己,他們只是感情好旧巾,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布耸序。 她就那樣靜靜地躺著,像睡著了一般鲁猩。 火紅的嫁衣襯著肌膚如雪坎怪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天廓握,我揣著相機與錄音搅窿,去河邊找鬼。 笑死疾棵,一個胖子當著我的面吹牛戈钢,可吹牛的內(nèi)容都是我干的痹仙。 我是一名探鬼主播是尔,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼开仰!你這毒婦竟也來了拟枚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤众弓,失蹤者是張志新(化名)和其女友劉穎恩溅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谓娃,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡脚乡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了滨达。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奶稠。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖捡遍,靈堂內(nèi)的尸體忽然破棺而出锌订,到底是詐尸還是另有隱情,我是刑警寧澤画株,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布辆飘,位于F島的核電站啦辐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蜈项。R本人自食惡果不足惜芹关,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望战得。 院中可真熱鬧充边,春花似錦、人聲如沸常侦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽聋亡。三九已至肘习,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坡倔,已是汗流浹背漂佩。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留罪塔,地道東北人投蝉。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓窍株,卻偏偏與公主長得像懊蒸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子优烧,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容