Codable 是swift4的新特性, 使用其可以用更少的程式碼更快的實現資料編解碼
JSON
一種基於文字的用於表示資訊的格式,人類和計算機都很容易讀寫這種格式,也是目前使用最廣泛的標準
簡單寫一個json文字
let json = """
{
"name" : "xiaoming",
"age" : 18,
"score" : 99.5
}
""".data(using: .utf8)!
複製程式碼
我們用上面的json來模擬網路資料請求得到的結果,下面構建model和解析
構建模型
我們建立一種適合這種json表示的swift型別 也就是model, 讓json中的值和swift系統中定義型別互相匹配。
- JSON中的物件是無序鍵值對,和swift中包含
String
的Dictionary
類似 - JSON 將數字表示成一系列數,不受任何語義的影響,它不區分整數和浮點數,不區分定長和非定長數字,也不區分是二進位制還是十進位制。每個實現自行決定如何解釋這些數字。
- 對於具有 Optional 屬性的型別, Codable 可以自動將 null 對映為 nil。
struct Student : Codable {
var name : String
var age : Int
var score : Double
}
複製程式碼
我們首先定義一個結構體 Student
來對應資料中的頂層物件, 這裡我們使用結構體,使用 Class
也無妨。
Codable 協議簡介
上面的結構體 Student
遵循了 Codable
協議, 其大大提升了物件和其表示之間相互轉換的體驗。
理解 Codable 最好的方式就是看它的定義:
typealias Codable = Decodable & Encodable
Codable
是一種 混合型別,由 Decodable
和 Encodable
協議構成。
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:)
初始化函式裡我們建立一個鍵控容器,呼叫decoder
的 container(keyedBy:)
方法,並傳入 CodingKeys
作為引數。
最後,我們呼叫 container
的 decode(_:,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
,以便使用 JSONDecoder
和 JSONEncoder
。
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:)
就像一對映象一樣工作。 先呼叫 encoder
的 container(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)不敏感,但是你可能會在意。如果你覺得輸出不美觀,把 JSONEncoder
的 outputFormatting
屬性設定為 .prettyPrinted
就好了
刪除無用的程式碼
把所有我們寫的 Codable
的實現都刪掉。這樣,應該就只剩下結構和屬性的定義了。
struct Student : Codable {
var name : String
var age : Int
var score : Double
}
複製程式碼
直接執行程式碼,試試編碼和解碼 JSON。有什麼變化嗎?完全沒有。 這讓我們看到了 Codable 的殺手級特性:
Swift 自動整合了 Decodable
和 Encodable
的一致性。
一種型別只要在其宣告中採用協議即可,也就是說,不用在擴充套件中再做額外的事,只要它的每個屬性都有一個符合其協議,其他一切都自動完成了。
嘗試解析各種格式的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)!
複製程式碼
如果 Array
和Dictionary
包含的 KeyType
和 ValueType
都遵從 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
是一種 混合型別,由Decodable
和Encodable
協議構成。- 只要一種型別中的每個屬性都遵從協議,在型別定義時宣告一下,Swift 就會自動整合
Decodable
和Encodable
。 - 如果
Array
或Dictionary
中的每一個元素都遵從Codable
型別,那麼它們本身也會遵從Codable