《Advanced Swift》第十二章 編碼和解碼:讀書筆記 零、概述 一、一個最小的例子 二、解碼過程 三、合成代碼 四、手動遵循協議 五、手動遵循協議 六、常見的編碼任務

零、概述

        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:) 方法。

三、合成代碼

四、手動遵循協議

五、手動遵循協議

六、常見的編碼任務

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章