零、概述
Swift的編碼系統設計圍繞以下幾個核心目標進行。
- 普遍性 - 對結構體,枚舉和類都適用。
- 類型安全 - 像 JSON 這樣的可交換格式通常都是弱類型的,而你的代碼應該使用強類型
數據結構。 - 減少模板代碼 - 當自定義類型加入這套系統時,應該儘可能減少開發者需要編寫的 “適
配代碼”,編譯器應該可以自動生成它們。
/// 一個類型可以將自身編碼爲某種外部表示形式。
public protocol Encodable {
/// 將值編碼到給定的 encoder 中。
public func encode(to encoder: Encoder) throws
}
/// 一個類型可以從某種外部表示形式中解碼得到自身。
public protocol Decodable {
/// 從給定的 decoder 中解碼來創建新的實例。
public init(from decoder: Decoder) throws
}
public typealias Codable = Decodable & Encodable
- 標準庫中的所有基本類型,包括 Bool,數值類型和 String,都是實現了 Codable 的類型。
- 如果數組,字典,Set 以及 Range 中包含的元素實現了 Codable,那麼這些類型自身也是 實現了 Codable 的類型。
- 最後,包括 Data,Date,URL,CGPoint 和 CGRect 在內的許多 Apple 框架中的常用數據類型,也已經適配了 Codable。
一、一個最小的例子
1.1 自動遵循協議
如果類型中所有的存儲屬性 都是可編解碼的,那麼 Swift 編譯器會自動幫你生成實現 Encodable 和 Decodable 協議的代 碼。
struct Coordinate: Codable {
var latitude: Double
var longitude: Double
//不需要實現
}
struct Placemark: Codable {
var name: String
var coordinate: Coordinate
//不需要實現
}
// 編譯器爲一個類型自動合成 Codable 協議的代碼是不可見的
1.1.1 Encoding
Swift 自帶兩個編碼器,分別是 JSONEncoder 和 PropertyListEncoder (它們定義在 Foundation 中,而不是在標準庫裏)。另外,實現了 Codable 的類型和 Cocoa 中的 NSKeyedArchiver 也是兼容的。
let places = [
Placemark(name: "Berlin", coordinate: Coordinate(latitude: 52, longitude: 13)),
Placemark(name: "Cape Town", coordinate: Coordinate(latitude: -34, longitude: 18))
]
do{
let encoder = JSONEncoder()
let jsonData = try encoder.encode(places) // 129 bytes
let jsonString = String(decoding: jsonData, as: UTF8.self)
/*
[{"name":"Berlin","coordinate":{"longitude":13,"latitude":52}}, {"name":"Cape Town","coordinate":{"longitude":18,"latitude":-34}}] */
} catch {
print(error.localizedDescription)
}
1.1.2 Decoding
do{
let decoder = JSONDecoder()
let decoded = try decoder.decode([Placemark].self, from: jsonData)
// [Berlin (lat: 52.0, lon: 13.0), Cape Town (lat: -34.0, lon: 18.0)]
type(of: decoded) // Array<Placemark>
decoded == places // true
} catch {
print(error.localizedDescription)
}
二、解碼過程
2.1 容器
/// 一個可以把值編碼成某種外部表現形式的類型。
public protocol Encoder {
/// 編碼到當前位置的編碼鍵 (coding key) 路徑
var codingPath: [CodingKey] { get }
/// 用戶爲編碼設置的上下文信息。
var userInfo: [CodingUserInfoKey : Any] { get }
/// 返回一個容器,用於存放多個由給定鍵索引的值。
func container<Key: CodingKey>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key>
/// 返回一個容器,用於存放多個沒有鍵索引的值。
func unkeyedContainer() -> UnkeyedEncodingContainer /// 返回一個適合存放單一值的編碼容器。
func singleValueContainer() -> SingleValueEncodingContainer
}
容器有三種類型:
- 鍵容器(KeyedContainer)用於編碼鍵值對。可以把鍵容器想像爲一個特殊的字典,這 是到目前爲止,應用最普遍的容器。
鍵容器內部使用的鍵是強類型的,這爲我們提供了類型安全和自動補全的特性。編碼器 最終會在寫入目標格式 (比如 JSON) 時,將鍵轉換爲字符串 (或者數字),不過這對開發 者來說是隱藏的。修改編碼後的鍵名是最簡單的一種自定義編碼方式的操作,我們將會 在下面看到一些相關的例子。 - 無鍵容器(UnkeyedContainer)用於編碼一系列值,但不需要對應的鍵,可以將它想像 成保存編碼結果的數組。因爲沒有對應的鍵來確定某個值,所以對無鍵容器中的值進行 解碼的時候,需要遵守和編碼時同樣的順序。
- 單值容器對單一值進行編碼。你可以用它來處理只由單個屬性定義的那些類型。例如: Int 這樣的原始類型,或以原始類型實現了 RawRepresentable 協議的枚舉。
/// 支持存儲和直接編碼無索引單一值的容器。 public protocol SingleValueEncodingContainer {
/// 編碼到當前位置的編碼鍵路徑。
var codingPath: [CodingKey] { get }
/// 編碼空值。
mutating func encodeNil() throws
/// 編碼原始類型的方法
mutating func encode(_ value: Bool) throws
mutating func encode(_ value: Int) throws
mutating func encode(_ value: Int8) throws
mutating func encode(_ value: Int16) throws
mutating func encode(_ value: Int32) throws
mutating func encode(_ value: Int64) throws
mutating func encode(_ value: UInt) throws
mutating func encode(_ value: UInt8) throws
mutating func encode(_ value: UInt16) throws
mutating func encode(_ value: UInt32) throws
mutating func encode(_ value: UInt64) throws
mutating func encode(_ value: Float) throws
mutating func encode(_ value: Double) throws
mutating func encode(_ value: String) throws
mutating func encode<T: Encodable>(_ value: T) throws
}
回到之前的例子,我們要編碼的頂層類型是 Array<Placemark>。而無鍵容器是保存數組編碼結 果的絕佳場所 (因爲數組說白了就是一串值的序列)。因此,數組將會向編碼器請求一個無鍵容 器。然後,對自身的元素進行迭代,並告訴容器對這些元素一一進行編碼。把這個過程用代碼 表示出來,是這樣的:
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)
}
}
}
數組中的元素是 Placemark 實例。之前我們已經說過,對於非原始類型的值,容器將繼續調用 這個值的 encode(to:) 方法。