轉(zhuǎn)載請(qǐng)注明,原文地址:Swift4 終極解析方案:基礎(chǔ)篇
做過(guò)網(wǎng)絡(luò)開(kāi)發(fā),特別是互聯(lián)網(wǎng),甚至移動(dòng)端開(kāi)發(fā)的,日常對(duì)于數(shù)據(jù)解析,早年主流的
XML,現(xiàn)今主流的JSON都是非常熟悉的,說(shuō)道解析,系統(tǒng)自帶和各種第三方的解析庫(kù),除了解析當(dāng)然也當(dāng)不了懶癌的腳步,各種model反射庫(kù)。
對(duì)于Objective-C各種方案都尤為成熟,甚至還有專門的MacApp用于model生成,可以說(shuō)是懶到極致,好處當(dāng)然是節(jié)省出了擼貓擼手辦的時(shí)間(技術(shù)狗擼手辦不知道是什么時(shí)候開(kāi)始的惡習(xí),我還是更喜歡擼妹紙)。
這種操作對(duì)于Swift就比較蛋疼了,當(dāng)然第三方庫(kù)和工具也是完全夠用,但是,生成的model里面一大堆代碼,一個(gè)字,惡心。
那好,今年Swift更新到4.0版本之后帶了一個(gè)我最喜歡的功能:Codable協(xié)議。
Codable是Encodable和Decodable協(xié)議總和的別名。所以它既能編碼也能解碼,自從有了它,我model里面代碼奏是干干凈凈,清清爽爽,對(duì)于潔癖控來(lái)說(shuō),這貨是原生的,又可以少個(gè)Pod和Package了,巴巴掌。
開(kāi)始吧
如果對(duì)此協(xié)議不太明白到底能干啥,可以先看下今年的WWDC視頻。
Codable讓我們可以通過(guò)Struct和Class不要一行多余代碼來(lái)解析JSON和Plist數(shù)據(jù)。基礎(chǔ)庫(kù)簡(jiǎn)直嗨的不要不要的。
讓我們來(lái)看一個(gè)例子:
import Foundation
struct Swifter: Decodable {
let fullName: String
let id: Int
let twitter: URL
}
let json = """
{
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
}
""".data(using: .utf8)! // our data in native (JSON) format
let myStruct = try JSONDecoder().decode(Swifter.self, from: json) // Decoding our data
print(myStruct) // decoded!!!!!
如果你看過(guò)Decodable文檔,那你肯定知道里面有一個(gè)必須實(shí)現(xiàn)的方法init(from: Decoder)。然而示例里并沒(méi)實(shí)現(xiàn),照樣跑得飛起來(lái),那就是蘋果爸爸媽寶當(dāng)?shù)郊遥壕幾g器默認(rèn)會(huì)幫我們實(shí)現(xiàn)一個(gè)。
再看看另外一個(gè)例子:
import Foundation
enum BeerStyle : String, Codable {
case ipa
case stout
case kolsch
// ...
}
struct Beer {
let name: String
let brewery: String
let style: BeerStyle
}
let json = """
{
"name": "Endeavor",
"abv": 8.9,
"brewery": "Saint Arnold",
"style": "ipa"
}
""".data(using: .utf8)! // our data in native (JSON) format
let myStruct = try JSONDecoder().decode(Swifter.self, from: json) // Decoding our data
print(myStruct) // decoded!!!!!
看到這里是不是就被戳到G點(diǎn)了???如果后端沒(méi)有啥特別的字段,你只需要把
JSON里的Key作為Property即可。
解析的條件就是,只要是系統(tǒng)提供的如String,number,Bool以及各類集合,只要是符合Decodable協(xié)議即可,簡(jiǎn)直是嗨到極點(diǎn)。
自定義實(shí)現(xiàn)
能夠直接解析當(dāng)然是最好的,但是往往開(kāi)發(fā)的時(shí)候會(huì)遇到一些比較復(fù)雜的結(jié)構(gòu),那可能是Array和Dictionary相互嵌套,各個(gè)系統(tǒng)或者開(kāi)發(fā)語(yǔ)言的保留字導(dǎo)致字段奇特,以及很多煞筆后端搞些魔術(shù)字啊,拼音命名的字段啥的。

實(shí)際上開(kāi)發(fā)大多遇到的都是這種情況,那就不得不出動(dòng)自定義解析了。自定義的部分稍微復(fù)雜點(diǎn),坐穩(wěn)了別翻車。
解碼器
Decoder負(fù)責(zé)處理JSON和Plist解析工作,需要重點(diǎn)關(guān)注的兩個(gè)方法:
public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey
public func singleValueContainer() throws -> SingleValueDecodingContainer
這兩個(gè)方法返回的都是Container。
第一個(gè)方法返回keyedContainer,KeyedDecodingContainer:如果我們要自定義解析,那就需要告訴解碼器如何映射。
第二個(gè)方法僅僅返回一個(gè)Container,而SingleValueDecodingContainer里的數(shù)據(jù)正是我們想要的。
容器
Decoder提供了基礎(chǔ)的功能解析原始數(shù)據(jù),自定義數(shù)據(jù)就需要我們自己來(lái)搞定。
KeyedDecodingContainer:我們的容器是通過(guò)鍵值匹配的,所以大可以看作[Key: Any]這樣的字典結(jié)構(gòu)。
不同的鍵對(duì)應(yīng)不同的類型數(shù)據(jù),所以容器提供的不同解碼方法:decode(Type:forKey:)。
它的神奇之處就在于容器會(huì)自動(dòng)匹配數(shù)據(jù)類型。
當(dāng)然,解碼器也提供了通用方法:
public func decode<T>(_ type: T.Type, forKey key: KeyedDecodingContainer.Key) throws -> T where T : Decodable
有了這個(gè)泛型解析方法,那就意味著任意類型都可以匹配解析。
SingleValueDecodingContainer就更前面說(shuō)的一樣,只考慮返回。
實(shí)現(xiàn)
前面基礎(chǔ)已經(jīng)鋪墊完了,現(xiàn)在就不要編譯器幫忙了,我們自己手動(dòng)來(lái)實(shí)現(xiàn)init(from: Decoder)。
第一步:選擇正確的解碼器
示例里是JSON對(duì)象,那我們就選擇JSONDecoder。
let decoder = JSONDecoder()
JSON和Plist解析器都是系統(tǒng)內(nèi)置的,如果你想要,你也可以自己實(shí)現(xiàn)一個(gè)解析器解析你夠奇葩的數(shù)據(jù)。
第二步:選擇正確的容器
JSON數(shù)據(jù)如下:
{
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
}
其實(shí)按照最開(kāi)始說(shuō)的,這個(gè)對(duì)象可以直接反射,辣么我們這里講的自定義,那就按照自定義的套路走,我們按要求實(shí)現(xiàn)一個(gè)String的結(jié)構(gòu)體,并且滿足CodingKey協(xié)議:
enum MyStructKeys: String, CodingKey {
case fullName = "fullName"
case id = "id"
case twitter = "twitter"
}
接下來(lái)創(chuàng)建容器:
let container = try decoder.container(keyedBy: MyStructKeys.self)
第三步:提取數(shù)據(jù)
這里我們需要做類型轉(zhuǎn)換:
let fullName: String = try container.decode(String.self, forKey: .fullName)
let id: Int = try container.decode(Int.self, forKey: .id)
let twitter: URL = try container.decode(URL.self, forKey: .twitter)
第四步:初始化
使用默認(rèn)的構(gòu)造器:
let myStruct = Swifter(fullName: fullName, id: id, twitter: twitter)
現(xiàn)在我們就來(lái)看看全部實(shí)現(xiàn):
import Foundation
struct Swifter {
let fullName: String
let id: Int
let twitter: URL
init(fullName: String, id: Int, twitter: URL) { // default struct initializer
self.fullName = fullName
self.id = id
self.twitter = twitter
}
}
extension Swifter: Decodable {
enum MyStructKeys: String, CodingKey { // declaring our keys
case fullName = "fullName"
case id = "id"
case twitter = "twitter"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: MyStructKeys.self) // defining our (keyed) container
let fullName: String = try container.decode(String.self, forKey: .fullName) // extracting the data
let id: Int = try container.decode(Int.self, forKey: .id) // extracting the data
let twitter: URL = try container.decode(URL.self, forKey: .twitter) // extracting the data
self.init(fullName: fullName, id: id, twitter: twitter) // initializing our struct
}
}
let json = """
{
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
}
""".data(using: .utf8)! // our native (JSON) data
let myStruct = try JSONDecoder().decode(Swifter.self, from: json) // decoding our data
print(myStruct) // decoded!
復(fù)雜結(jié)構(gòu)處理
數(shù)組
import Foundation
struct Swifter: Decodable {
let fullName: String
let id: Int
let twitter: URL
}
let json = """
[{
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
},{
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
},{
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
}]
""".data(using: .utf8)! // our data in native format
let myStructArray = try JSONDecoder().decode([Swifter].self, from: json)
myStructArray.forEach { print($0) } // decoded!!!!!
字典
import Foundation
struct Swifter: Codable {
let fullName: String
let id: Int
let twitter: URL
}
let json = """
{
"one": {
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
},
"two": {
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
},
"three": {
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
}
}
""".data(using: .utf8)! // our data in native format
let myStructDictionary = try JSONDecoder().decode([String: Swifter].self, from: json)
myStructDictionary.forEach { print("\($0.key): \($0.value)") } // decoded!!!!!
枚舉
import Foundation
struct Swifter: Decodable {
let fullName: String
let id: Int
let twitter: URL
}
enum SwifterOrBool: Decodable {
case swifter(Swifter)
case bool(Bool)
}
extension SwifterOrBool: Decodable {
enum CodingKeys: String, CodingKey {
case swifter, bool
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let swifter = try container.decodeIfPresent(Swifter.self, forKey: .swifter) {
self = .swifter(swifter)
} else {
self = .bool(try container.decode(Bool.self, forKey: .bool))
}
}
}
let json = """
[{
"swifter": {
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
}
},
{ "bool": true },
{ "bool": false },
{
"swifter": {
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
}
}]
""".data(using: .utf8)! // our native (JSON) data
let myEnumArray = try JSONDecoder().decode([SwifterOrBool].self, from: json) // decoding our data
myEnumArray.forEach { print($0) } // decoded!
嵌套結(jié)構(gòu)
import Foundation
struct Swifter: Decodable {
let fullName: String
let id: Int
let twitter: URL
}
struct MoreComplexStruct: Decodable {
let swifter: Swifter
let lovesSwift: Bool
}
let json = """
{
"swifter": {
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
},
"lovesSwift": true
}
""".data(using: .utf8)! // our data in native format
let myMoreComplexStruct = try JSONDecoder().decode(MoreComplexStruct.self, from: json)
print(myMoreComplexStruct.swifter) // decoded!!!!!
結(jié)尾
為了避免必要的情況,也為了增強(qiáng)代碼的健壯性,我們應(yīng)該多使用系統(tǒng)的錯(cuò)誤處理來(lái)避免經(jīng)常崩潰:
do {
let myStruct = try JSONDecoder().decode(Swifter.self, from: json) // do your decoding here
} catch {
print(error) // any decoding error will be printed here!
}