Swift5 從理解編碼對象的流程開始

該如何研究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中JSONEncoderJSONDecoder的實現(xiàn)。在這個系列里,我們就用這兩個類型作為代表來研究對象的編碼和解碼過程了;

接下來,我們就從Codable說起,它的定義在這里

public typealias Codable = Encodable & Decodable

看到了吧,實際上它只是一個別名而已。而EncodableDecodable則是兩個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)容用一張圖表示,就是這樣的:

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young閱讀 4,200評論 1 10
  • 123.繼承 一個類可以從另外一個類繼承方法,屬性和其他特征。當一個類繼承另外一個類時, 繼承類叫子類, 被繼承的...
    無灃閱讀 1,497評論 2 4
  • Swift 介紹 簡介 Swift 語言由蘋果公司在 2014 年推出,用來撰寫 OS X 和 iOS 應(yīng)用程序 ...
    大L君閱讀 3,436評論 3 25
  • 1、范型范型所解決的問題 函數(shù)、方法、類型:類,結(jié)構(gòu)體,枚舉,元組類型,協(xié)議參數(shù),返回值,成員函數(shù)參數(shù),成員屬性類...
    我是小胡胡123閱讀 945評論 0 1
  • 基礎(chǔ)部分(The Basics) 當推斷浮點數(shù)的類型時,Swift 總是會選擇Double而不是Float。 結(jié)合...
    gamper閱讀 1,496評論 0 7

友情鏈接更多精彩內(nèi)容