Swift5 支持自定義編碼的三種容器

我們來研究構(gòu)成__JSONEncoder的另一半囊扳,也就是它的Encoder身份。

Encoder約束了什么

既然如此蒂阱,我們當(dāng)然應(yīng)該從Encoder究竟約束了什么開始踏揣。這個protocol的定義在這里

public protocol Encoder {
  var codingPath: [CodingKey] { get }
  var userInfo: [CodingUserInfoKey : Any] { get }

  func container<Key>(keyedBy type: Key.Type)
    -> KeyedEncodingContainer<Key>

  func unkeyedContainer() -> UnkeyedEncodingContainer

  func singleValueContainer() -> SingleValueEncodingContainer
}

其中庆亡,codingPathuserInfo在之前我們已經(jīng)看過了。現(xiàn)在的重點呼伸,就是Encoder約束的這三個方法身冀,或者說,是這三個方法的返回值:KeyedEncodingContainer<Key> / UnkeyedEncodingContainer / SingleValueEncodingContainer括享。這些叫做container的類型究竟是做什么的呢?其實它們的名字遠比它們實際的作用看起來要高級多了珍促。為了感性了解下這三個容器類型的作用铃辖,我們先來看個例子,假設(shè)要編碼下面這個自定義的類型:

struct User {
  let name: String
  let age: Double
}

我們姑且不說Swift可以為這種類型自動添加Encodable能力這回事兒(因為它的每個屬性都遵從Encodable)≈硇穑現(xiàn)在娇斩,就是得自定義它的編碼方法仁卷,第一種方法,就是用KeyedEncodingContainer

extension User: Encodable {
  private enum CodingKeys: CodingKey {
    case name
    case age
  }

  public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(name, forKey: .name)
    try container.encode(age, forKey: .age)
  }
}

用下面的代碼測試一下:

let elev = User(name: "11", age: 11)
let data = try JSONEncoder().encode(elev)
let str = String(bytes: data, encoding: .utf8)!

print(str)

就可以得到{"name":"11","age":11}這樣的結(jié)果了犬第。參照這個結(jié)果以及剛才encode(to:)的實現(xiàn)锦积,不難猜到,所謂的KeyedEncodingContainer歉嗓,就是可以讓我們按照CodingKeys中指定的key丰介,把要編碼的值存起來的容器。至于這個CodingKey類型鉴分,我們找后找個機會再來說它∠保現(xiàn)在只要知道用它給container定義key的名字就行了。

接下來志珍,再來看用UnkeyedEncodingContainer來編碼User對象會如何橙垢,我們把之前的代碼改成這樣:

public func encode(to encoder: Encoder) throws {
  var container = encoder.unkeyedContainer()
  try container.encode(name)
  try container.encode(age)
}

執(zhí)行之前相同的測試代碼,就會得到這樣的結(jié)果:["11",11]伦糯」衲常看到了吧,既然是UnKeyed敛纲,那就是結(jié)果中只有值莺琳,沒有key了唄。這樣編碼出來的载慈,就是一個只包含值的數(shù)組惭等。

至于最后一個SingleValueEncodingContainer,它表示的就是一個編碼單一值的容器办铡,我們只能向其中添加一個值:

public func encode(to encoder: Encoder) throws {
  var container = encoder.singleValueContainer()
  try container.encode(name)
  /// Uncomment the following line will cause runtime error.
  /// try container.encode(age)
}

就像上面注釋的一樣辞做,如果去掉編碼age的注釋,就會發(fā)生運行時錯誤寡具。但即便是這樣秤茅,我們之前使用的測試代碼還是無法正常工作。因為通過SingleValueEncodingContainer編碼出來的結(jié)果童叠,是一個NSString框喳,而不是一個NSDictonary或者NSArray。上一節(jié)厦坛,我們分析encode()方法的時候就看到了五垮,如果box_返回的不是一個NS開頭的集合類型,都會發(fā)生運行時錯誤杜秸。因此放仗,如果我們把User用了SingleValueEncodingContainer編碼,那么測試的時候撬碟,就得用這個值手工構(gòu)建一個集合類型:

let elev = User(name: "11", age: 11)
let data = try JSONEncoder().encode([elev])
let str = String(bytes: data, encoding: .utf8)!

print(str)

這樣诞挨,就可以得到["11"]這樣的結(jié)果了莉撇。不過這只是一個用于演示的例子,我們當(dāng)然不會只編碼User對象的某一個屬性惶傻。

看過了這三個例子之后棍郎,現(xiàn)在至少可以感性的知道了,如果把JSONEncoder比喻成一臺加工數(shù)據(jù)的機器银室,所謂Encoder約束的這些方法返回的containers涂佃,就好比為這個機器提交的加工規(guī)格表單,我們可以填寫key-value形式的粮揉,可以添加數(shù)組形式的巡李,也可以填寫加工成一個單一值形式的。

__JSONEncoder中container的實現(xiàn)

理解了剛才這些例子扶认,我們就可以反著追回去看看__JSONEncoder中侨拦,這三個方法究竟是如何實現(xiàn)的了。

SingleValueEncodingContainer

我們從最簡單的singleValueContainer方法說起辐宾,它的定義在這里

fileprivate class __JSONEncoder : Encoder {
  public func singleValueContainer()
    -> SingleValueEncodingContainer {
      return self
  }
}

看到了吧狱从,就是返回它自己,也就是說叠纹,__JSONEncoder自身就是一個SingleValueEncodingContainer季研。那這個SingleValueEncodingContainer又是什么呢?實際上誉察,它是一個定義在這里protocol

public protocol SingleValueEncodingContainer {
  var codingPath: [CodingKey] { get }

  mutating func encodeNil() throws

% for type in codable_types:
  mutating func encode(_ value: ${type}) throws
% end

  mutating func encode<T : Encodable>(_ value: T) throws
}

也就是說与涡,它為nilcodable_types中定義的類型以及任何一個遵從了Encodable的類型持偏,定義了一個編碼它們的encode方法驼卖。既然__JSONEncoder遵從了SingleValueEncodingContainer,那我們接下來當(dāng)然就是去看看__JSONEncoder是如何實現(xiàn)這些方法的了鸿秆。這個extension的定義酌畜,在這里。對于protocol中約束的每一類encode卿叽,我們各列舉一個實現(xiàn):

extension __JSONEncoder : SingleValueEncodingContainer {
  // MARK: - SingleValueEncodingContainer Methods
  fileprivate func assertCanEncodeNewValue() {
    precondition(self.canEncodeNewValue,
      "Attempt to encode value through single value container when previously value already encoded.")
  }

  public func encodeNil() throws {
    assertCanEncodeNewValue()
    self.storage.push(container: NSNull())
  }

  public func encode(_ value: Bool) throws {
    assertCanEncodeNewValue()
    self.storage.push(container: self.box(value))
  }

  public func encode<T : Encodable>(_ value: T) throws {
    assertCanEncodeNewValue()
    try self.storage.push(container: self.box(value))
  }
}

其中桥胞,canEncodeNewValue的實現(xiàn)在這里

fileprivate var canEncodeNewValue: Bool {
    return self.storage.count == self.codingPath.count
}

其實,在官方源代碼里考婴,這個計算屬性有一大段注釋贩虾,大家可以稍后自己去看看。現(xiàn)在蕉扮,只要知道整胃,所謂可以編碼新值的要求,就是__JSONEncoder.storage中元素的個數(shù)和codingPath中元素的個數(shù)相同就行了喳钟。

至于是編碼nil屁使、codable_types中定義的類型還是任何一個遵從了Encodable的類型,其實它們的邏輯都是一樣的奔则,把這些值用box方法打包之后蛮寂,存入__JSONEncoder.storage就行了。

看到這易茬,想必大家也就明白為什么SingleValueEncodingContainer只能編碼一個值了酬蹋。在向storage中存入元素之后,self.storage.count的值會增加抽莱,但是編碼進來的值并沒有對應(yīng)的codingPath范抓。當(dāng)我們繼續(xù)向其中編碼新值的時候,canEncodeNewValue就會返回false食铐,進而precondition就會觸發(fā)錯誤了匕垫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市虐呻,隨后出現(xiàn)的幾起案子象泵,更是在濱河造成了極大的恐慌,老刑警劉巖斟叼,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偶惠,死亡現(xiàn)場離奇詭異,居然都是意外死亡朗涩,警方通過查閱死者的電腦和手機忽孽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谢床,“玉大人兄一,你說我怎么就攤上這事∮┿玻” “怎么了瘾腰?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長覆履。 經(jīng)常有香客問我蹋盆,道長,這世上最難降的妖魔是什么硝全? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任栖雾,我火速辦了婚禮,結(jié)果婚禮上伟众,老公的妹妹穿的比我還像新娘析藕。我一直安慰自己,他們只是感情好凳厢,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布账胧。 她就那樣靜靜地躺著竞慢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪治泥。 梳的紋絲不亂的頭發(fā)上筹煮,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機與錄音居夹,去河邊找鬼败潦。 笑死,一個胖子當(dāng)著我的面吹牛准脂,可吹牛的內(nèi)容都是我干的劫扒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼狸膏,長吁一口氣:“原來是場噩夢啊……” “哼沟饥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起环戈,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤闷板,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后院塞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遮晚,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年拦止,在試婚紗的時候發(fā)現(xiàn)自己被綠了县遣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡汹族,死狀恐怖萧求,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情顶瞒,我是刑警寧澤夸政,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站榴徐,受9級特大地震影響守问,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坑资,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一耗帕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧袱贮,春花似錦仿便、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荒勇。三九已至,卻和暖如春钦幔,著一層夾襖步出監(jiān)牢的瞬間枕屉,已是汗流浹背常柄。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工鲤氢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人西潘。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓卷玉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親喷市。 傳聞我的和親對象是個殘疾皇子相种,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,129評論 25 707
  • 這個世界上,最幸福的事是在一個美好的環(huán)境下死去 在一個夏至之后的夜晚 看見夕陽的余暉籠罩在城市的西邊 看見...
    十嘢閱讀 479評論 0 1
  • 說好的早睡早起,僅僅是說好而已腹备。因為我們是害怕晚上的寂寞和對抗不了睡懶覺的惰性衬潦。自從有了智能機互聯(lián)網(wǎng),世界是連起來...
    猴子不說話閱讀 378評論 0 0
  • 大長腿先生是我家兒子小L植酥,常自詡豐神俊秀玉樹臨風(fēng)謙謙君子才辯無雙引校內(nèi)無數(shù)美女競折腰镀岛。我這個媽盡量客觀,他似乎并沒...
    步綰閱讀 3,536評論 135 95