我們來研究構(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
}
其中庆亡,codingPath
和userInfo
在之前我們已經(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
}
也就是說与涡,它為nil
,codable_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ā)錯誤了匕垫。