iOS 學習使用 Swift Codable

Codable 是swift4的新特性, 使用其能夠用更少的代碼更快的實現數據編解碼json

JSON

一種基於文本的用於表示信息的格式,人類和計算機都很容易讀寫這種格式,也是目前使用最普遍的標準swift

簡單寫一個json文本數組

let json = """
{
    "name" : "xiaoming",
    "age" : 18,
    "score" : 99.5

}
""".data(using: .utf8)!
複製代碼

咱們用上面的json來模擬網絡數據請求獲得的結果,下面構建model和解析bash

構建模型

咱們建立一種適合這種json表示的swift類型 也就是model, 讓json中的值和swift系統中定義類型互相匹配。網絡

  • JSON中的對象是無序鍵值對,和swift中包含 StringDictionary 相似
  • JSON 將數字表示成一系列數,不受任何語義的影響,它不區分整數和浮點數,不區分定長和非定長數字,也不區分是二進制仍是十進制。每一個實現自行決定如何解釋這些數字。
  • 對於具備 Optional 屬性的類型, Codable 能夠自動將 null 映射爲 nil。
struct Student : Codable {
    var name : String
    var age : Int
    var score : Double
}
複製代碼

咱們首先定義一個結構體 Student 來對應數據中的頂層對象, 這裏咱們使用結構體,使用 Class 也無妨。函數

Codable 協議簡介

上面的結構體 Student 遵循了 Codable 協議, 其大大提高了對象和其表示之間相互轉換的體驗。ui

理解 Codable 最好的方式就是看它的定義:編碼

typealias Codable = Decodable & Encodablespa

Codable 是一種 混合類型,由 DecodableEncodable 協議構成。code

Decodable 協議定義了一個初始化函數:

init(from decoder: Decoder) throws

聽從 Decodable 協議的類型可使用任何 Decoder 對象進行初始化。

Encodable 協議定義了一個方法:

func encode(to encoder: Encoder) throws

任何 Encoder 對象均可以建立聽從了 Encodable 協議類型的表示。

只有當一個類型知足這個協議的全部要求的時候,咱們才能說這個類型 聽從 那個協議了。 對於 Decodable 而言,惟一的要求就是 init(from:) 初始化方法。

init(from:) 初始化方法接受一個 Decoder 參數。 Decoder 協議須要闡明將 Decodable 對象的表示解碼成對象的要求。爲了適應各類數據交換格式,解碼器和編碼器都使用名爲 容器(container)的抽象。容器是用來存儲值的,能夠存儲一個值也能夠存儲多個值,能夠像字典同樣有鍵去對應值,也能夠像數組同樣不須要鍵。...

由於上面那個 JSON 表示的頂層有一個對象, 因此咱們就建立一個有鍵的容器,而後用不一樣的鍵來解碼各個屬性。

咱們建立一個 CodingKeys 枚舉,定義屬性名稱和容器的鍵之間的映射。這個枚舉聲明其原始類型是 String,同時聲明使用 CodingKey 協議。由於每一個名字都和 JSON 中的鍵相同,因此咱們不用爲這個枚舉提供明確的原始值。

struct Student: Decodable {
    // ...

    private enum CodingKeys: String, CodingKey {
        case name
        case age
        case score
    }
}...
複製代碼

接下來,在 init(from:) 初始化函數裏咱們建立一個鍵控容器,調用decodercontainer(keyedBy:) 方法,並傳入 CodingKeys 做爲參數。

最後,咱們調用 containerdecode(_:,forKey:) 方法把每一個屬性初始化一下。

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.name = try container.decode(String.self, forKey: .name)
    self.age = try container.decode(Int.self, forKey: .age)
    self.score = try container.decode(Double.self, forKey: .score)
}...
複製代碼

咱們有了一個 Student 模型,也聽從了 Decodable 協議,如今咱們能夠從 JSON 表示中建立一個 student 對象了

把 JSON 解碼成模型對象

先引用 Foundation,以便使用 JSONDecoderJSONEncoder

import Foundation

建立一個 JSONDecoder 對象並調用其 decode(_:from:) 方法

let decoder = JSONDecoder()
let student = try! decoder.decode(Student.self, from: json)
複製代碼

試驗轉換成功與否

print(student.name) 
print(student.age) 
print(student.score) 
複製代碼

將模型對象編碼爲 JSON

下面咱們實現 Encodable 協議要求的 encode(to:) 方法。 encode(to:)init(from:) 就像一對鏡像同樣工做。 先調用 encodercontainer(keyedBy:) 方法建立一個 container, 和上一步同樣,傳入一個 CodingKeys.self 參數。 這裏咱們把 container 看成一個變量(使用 var 而不是 let), 由於這個方法作的是填充 encoder 的參數,須要進行修改。咱們對每一個屬性調用一次 encode(_:forKey:) 並傳入屬性的值和它對應的鍵。...

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

複製代碼
let encoder = JSONEncoder()
let reencodedJSON = try! encoder.encode(stuent)

print(String(data: reencodedJSON, encoding: .utf8)!)...

複製代碼

JSON 對空格和空行(whitespace)不敏感,可是你可能會在乎。若是你以爲輸出不美觀,把 JSONEncoderoutputFormatting 屬性設置爲 .prettyPrinted 就行了

刪除無用的代碼

把全部咱們寫的 Codable 的實現都刪掉。這樣,應該就只剩下結構和屬性的定義了。

struct Student : Codable {
    var name : String
    var age : Int
    var score : Double
}
複製代碼

直接運行代碼,試試編碼和解碼 JSON。有什麼變化嗎?徹底沒有。 這讓咱們看到了 Codable 的殺手級特性:

Swift 自動整合了 DecodableEncodable 的一致性。

一種類型只要在其聲明中採用協議便可,也就是說,不用在擴展中再作額外的事,只要它的每一個屬性都有一個符合其協議,其餘一切都自動完成了。

嘗試解析各類格式的JSON文本

JSON頂層是數組的狀況:

let jsonModels = """ [ { "name" : "xiaoming", "age" : 18, "score" : 99.5 }, { "name" : "daxiong", "age" : 19, "score" : 66 } ] """.data(using: .utf8)!
複製代碼

把它解析成 Student 對象的數組十分簡單:像以前同樣調用 decode(_:from:),把 Student.self 換成 [Student].self 以便去解析 Student 數組而不是單個對象。

let students = try! decoder.decode([Student].self, from: jsonModels)

爲何這麼簡單就能夠了?這要歸功於 Swift 4 的另外一個新特性:條件一致性。

// swift/stdlib/public/core/Codable.swift.gyb
extension Array : Decodable where Element : Decodable {
    // ...
}

複製代碼

咱們再在數組的外層套一個字典的形式:

let jsonComplex = """ { "students" : [ { "name" : "xiaoming", "age" : 18, "score" : 99.5 }, { "name" : "daxiong", "age" : 19, "score" : 66 } ] } """.data(using: .utf8)!

複製代碼

若是 ArrayDictionary 包含的 KeyTypeValueType 都聽從 Decodable 協議,那麼這樣的數組或字典自己也就聽從了 Decodable 協議:

// swift/stdlib/public/core/Codable.swift.gyb
extension Dictionary : Decodable where Key : Decodable, 
                                       Value : Decodable {
    // ...
}...
複製代碼

所以你能夠把以前 decode(_:from:) 的調用參數直接換成 [String: [Student]].self (Dictionary<String, Plane> 的簡寫):

let jsonData = try! decoder.decode([String: [Student]].self, from: jsonComplex)
        let students = jsonData["students"]
複製代碼

或者你也能夠建立一個聽從 Decodable 協議的新類型,而後給這個類型一個屬性來放 [Student] ,該屬性的名字應和 JSON 中的鍵名相對應,再把這個新類型傳入 decode(_:from:) 函數中:

struct Brige: Decodable {
    var students: [Student]
}

let Brige = try! decoder.decode(Brige.self, from: json)
let students = Brige.students...
複製代碼
  • Codable 是一種 混合類型,由 DecodableEncodable 協議構成。
  • 只要一種類型中的每一個屬性都聽從協議,在類型定義時聲明一下,Swift 就會自動整合 DecodableEncodable
  • 若是 ArrayDictionary 中的每個元素都聽從 Codable 類型,那麼它們自己也會聽從 Codable
相關文章
相關標籤/搜索