Swift4 終極解析方案:基礎(chǔ)篇

轉(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é)議蓉坎。

CodableEncodableDecodable協(xié)議總和的別名澳眷。所以它既能編碼也能解碼,自從有了它蛉艾,我model里面代碼奏是干干凈凈钳踊,清清爽爽,對(duì)于潔癖控來說勿侯,這貨是原生的拓瞪,又可以少個(gè)PodPackage了,巴巴掌助琐。

開始吧

如果對(duì)此協(xié)議不太明白到底能干啥祭埂,可以先看下今年的WWDC視頻。

Codable讓我們可以通過StructClass不要一行多余代碼來解析JSONPlist數(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初家,numberBool以及各類集合耕突,只要是符合Decodable協(xié)議即可笤成,簡(jiǎn)直是嗨到極點(diǎn)。

自定義實(shí)現(xiàn)

能夠直接解析當(dāng)然是最好的眷茁,但是往往開發(fā)的時(shí)候會(huì)遇到一些比較復(fù)雜的結(jié)構(gòu),那可能是ArrayDictionary相互嵌套纵诞,各個(gè)系統(tǒng)或者開發(fā)語(yǔ)言的保留字導(dǎo)致字段奇特上祈,以及很多煞筆后端搞些魔術(shù)字啊,拼音命名的字段啥的浙芙。

實(shí)際上開發(fā)大多遇到的都是這種情況登刺,那就不得不出動(dòng)自定義解析了。自定義的部分稍微復(fù)雜點(diǎn)嗡呼,坐穩(wěn)了別翻車纸俭。

解碼器

Decoder負(fù)責(zé)處理JSONPlist解析工作,需要重點(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()

JSONPlist解析器都是系統(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!
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市氛改,隨后出現(xiàn)的幾起案子帐萎,更是在濱河造成了極大的恐慌,老刑警劉巖胜卤,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疆导,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡葛躏,警方通過查閱死者的電腦和手機(jī)澈段,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來紫新,“玉大人均蜜,你說我怎么就攤上這事∶⒙剩” “怎么了囤耳?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)偶芍。 經(jīng)常有香客問我充择,道長(zhǎng),這世上最難降的妖魔是什么匪蟀? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任椎麦,我火速辦了婚禮,結(jié)果婚禮上材彪,老公的妹妹穿的比我還像新娘观挎。我一直安慰自己琴儿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布嘁捷。 她就那樣靜靜地躺著造成,像睡著了一般。 火紅的嫁衣襯著肌膚如雪雄嚣。 梳的紋絲不亂的頭發(fā)上晒屎,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音缓升,去河邊找鬼鼓鲁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛港谊,可吹牛的內(nèi)容都是我干的骇吭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼歧寺,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼绵跷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起成福,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荆残,沒想到半個(gè)月后奴艾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡内斯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蕴潦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俘闯。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡潭苞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出真朗,到底是詐尸還是另有隱情此疹,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布遮婶,位于F島的核電站蝗碎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏旗扑。R本人自食惡果不足惜蹦骑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望臀防。 院中可真熱鬧眠菇,春花似錦边败、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至缕坎,卻和暖如春怖侦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谜叹。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工匾寝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荷腊。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓艳悔,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親女仰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子猜年,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)疾忍,斷路器乔外,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,162評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件一罩、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,105評(píng)論 4 62
  • 昨天沒有發(fā)杨幼,今天的話生物1.5h 數(shù)學(xué)20min明天繼續(xù)寫 看書3h
    bia唧媽閱讀 248評(píng)論 0 0
  • 我不做太遙遠(yuǎn)的夢(mèng),這讓我的睡眠安恬聂渊;我不去緬懷往事差购,因?yàn)榛夭坏竭^去;我小心地去愛別人汉嗽,因?yàn)槲也粣鄯簽E欲逃。我想哭的時(shí)候...
    高冷的小小櫻閱讀 185評(píng)論 0 1