轉(zhuǎn)載請(qǐng)注明,原文地址:Swift4 終極解析方案:基礎(chǔ)篇
做過網(wǎng)絡(luò)開發(fā)鸠天,特別是互聯(lián)網(wǎng)讼育,甚至移動(dòng)端開發(fā)的,日常對(duì)于數(shù)據(jù)解析,早年主流的
XML
奶段,現(xiàn)今主流的JSON
都是非常熟悉的饥瓷,說道解析,系統(tǒng)自帶和各種第三方的解析庫(kù)痹籍,除了解析當(dāng)然也當(dāng)不了懶癌的腳步呢铆,各種model反射庫(kù)。
對(duì)于Objective-C
各種方案都尤為成熟蹲缠,甚至還有專門的MacApp用于model生成棺克,可以說是懶到極致,好處當(dāng)然是節(jié)省出了擼貓擼手辦的時(shí)間(技術(shù)狗擼手辦不知道是什么時(shí)候開始的惡習(xí)线定,我還是更喜歡擼妹紙)娜谊。
這種操作對(duì)于Swift
就比較蛋疼了,當(dāng)然第三方庫(kù)和工具也是完全夠用斤讥,但是纱皆,生成的model里面一大堆代碼,一個(gè)字芭商,惡心派草。
那好,今年Swift更新到4.0版本之后帶了一個(gè)我最喜歡的功能:Codable協(xié)議蓉坎。
Codable
是Encodable和Decodable協(xié)議總和的別名澳眷。所以它既能編碼也能解碼,自從有了它蛉艾,我model里面代碼奏是干干凈凈钳踊,清清爽爽,對(duì)于潔癖控來說勿侯,這貨是原生的拓瞪,又可以少個(gè)Pod
和Package
了,巴巴掌助琐。
開始吧
如果對(duì)此協(xié)議不太明白到底能干啥祭埂,可以先看下今年的WWDC視頻。
Codable
讓我們可以通過Struct
和Class
不要一行多余代碼來解析JSON
和Plist
數(shù)據(jù)兵钮∏穑基礎(chǔ)庫(kù)簡(jiǎn)直嗨的不要不要的。
讓我們來看一個(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!!!!!
如果你看過Decodable文檔掘譬,那你肯定知道里面有一個(gè)必須實(shí)現(xiàn)的方法init(from: Decoder)
泰演。然而示例里并沒實(shí)現(xiàn),照樣跑得飛起來葱轩,那就是蘋果爸爸媽寶當(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)了藐握??垃喊?如果后端沒有啥特別的字段猾普,你只需要把
JSON
里的Key
作為Property
即可。
解析的條件就是本谜,只要是系統(tǒng)提供的如String
初家,number
,Bool
以及各類集合耕突,只要是符合Decodable
協(xié)議即可笤成,簡(jiǎn)直是嗨到極點(diǎn)。
自定義實(shí)現(xiàn)
能夠直接解析當(dāng)然是最好的眷茁,但是往往開發(fā)的時(shí)候會(huì)遇到一些比較復(fù)雜的結(jié)構(gòu),那可能是Array
和Dictionary
相互嵌套纵诞,各個(gè)系統(tǒng)或者開發(fā)語(yǔ)言的保留字導(dǎo)致字段奇特上祈,以及很多煞筆后端搞些魔術(shù)字啊,拼音命名的字段啥的浙芙。
實(shí)際上開發(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ù)就需要我們自己來搞定简珠。
KeyedDecodingContainer
:我們的容器是通過鍵值匹配的,所以大可以看作[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
就更前面說的一樣攘宙,只考慮返回屯耸。
實(shí)現(xiàn)
前面基礎(chǔ)已經(jīng)鋪墊完了,現(xiàn)在就不要編譯器幫忙了蹭劈,我們自己手動(dòng)來實(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í)按照最開始說的塔逃,這個(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"
}
接下來創(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)在我們就來看看全部實(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ò)誤處理來避免經(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!
}