在swift 4.0之后旭绒,官方提供了Codable
來解析json為對應(yīng)的Model,這比一些第三方框架簡單優(yōu)雅了許多,并且由于它是官方的袋倔,穩(wěn)定性也有了很好的保障扇售。
具體如何使用Codable來映射解析json并不是本文的重點巷懈,相信大家也都知道如何使用欧引,本文主要討論在解析枚舉數(shù)據(jù)的時候遇到的問題婚苹,特別是可變枚舉的情況廓译。
常規(guī)enum
對于后端接口數(shù)據(jù)中有一些確定值范圍的字段,比如性別這種,我們會使用枚舉來解析這個字段。假如我們有如下這樣的一個json數(shù)據(jù),下文增加修改字段都是在該json的基礎(chǔ)上進(jìn)行的:
{
"name": "rocky",
"sex": "male"
}
我們和后端約定sex字段只能傳遞字符串male
凄鼻、female
(當(dāng)然峭范,為了減少數(shù)據(jù)量,也會使用Int類型來表示對應(yīng)的值)褪尝,因此我們可以很容易的寫出來對應(yīng)的Person模型:
struct Person: Codable {
let name: String
let sex: Sex
enum Sex: String, Codable {
case male
case female
}
}
借助于JSONDecoder
我們可以將json轉(zhuǎn)化為對應(yīng)的Person:
let person_json_data = JSONSerialization.data(withJSONObject: person_json, options: [.fragmentsAllowed])
let person = JSONDecoder().decode(Person.self, from: person_json_data)
但是在這個過程中佳吞,如果后端增加了一個sex字段的值:unknown鹊汛,而且我們解析json的代碼已經(jīng)發(fā)版上線了,只要后端修改過后的接口一上線,就會解析失敗蛔添。
除了后端對該值做版本控制之外,我們別無他法,雖然版本控制修補丁還可以首量,如果涉及到大面積的字段有變動柄延,那就會割裂后端同學(xué)的代碼邏輯。
如果一開始不使用枚舉,而是直接使用String伪嫁、Int來保存數(shù)據(jù)就不會有這樣的問題了,既然我們這里討論的是Codable下對enum的解析
提鸟,那么這種case就不用考慮了。
使用OptionSet
上面的情況一般的發(fā)生主要在于業(yè)務(wù)變動,業(yè)務(wù)變動是無法預(yù)期的北发,但是我們可以使用OptionSet
來提前預(yù)防這種情況恶耽。
OptionSet一般用來可以組合的枚舉,通過或
、與
操作來達(dá)到對枚舉的組合拆祈,比如UIKit中設(shè)置圓角的UIRectCorner
就是他的一個應(yīng)用:
struct UIRectCorner : OptionSet {
static var topLeft: UIRectCorner { get }
static var topRight: UIRectCorner { get }
static var bottomLeft: UIRectCorner { get }
static var bottomRight: UIRectCorner { get }
static var allCorners: UIRectCorner { get }
}
雖然看起來不是枚舉溉苛,但是卻可以達(dá)到枚舉的效果,我們可以使用OptionSet來實現(xiàn)一個具有兼容性的枚舉彰檬。
假如現(xiàn)在json中新增一個level的字段挚币,這個字段我們使用Int類存儲:
{
...
"level": 2,
...
}
對應(yīng)的,Person中也需要新增一個Level的struct來解析這個字段:
struct Person: Codable {
...
let level: Level
struct Level: Codable, OptionSet {
typealias RawValue = Int
var rawValue: Int
init(rawValue: Int) {
self.rawValue = rawValue
}
static let low = Level(rawValue: 1 << 0) // 1
static let middle = Level(rawValue: 2 << 0) // 2
static let high = Level(rawValue: 3 << 0) // 3
}
}
這個時候润努,通過JSONDecoder
我們解析得到的level為middle
裁着。
如果接口中新增了level相關(guān)的字段,比如:higher扔罪,值為4敞咧,使用以前的代碼解析出來的level.rawValue就為4测砂,而不是已經(jīng)設(shè)置好的靜態(tài)對象纵东,最主要的是不會解析失敗。
使用OptionSet之后子檀,依然可以使用if-else镊掖、switch來判斷具體的case乃戈,以switch為例:
switch person.level {
case .low:
print("level low \(person.level.rawValue)")
case .middle:
print("level middle \(person.level.rawValue)")
case .high:
print("level high \(person.level.rawValue)")
default:
print("level unknow \(person.level.rawValue)")
}
既兼顧了enum的特性又具備了可拓展的特點。
問題
雖然使用OptionSet之后我們的數(shù)據(jù)解析代碼邏輯更健壯了亩进,同時也會帶來一些問題症虑。
首先就是代碼量增加了很多。針對這一點归薛,我認(rèn)為是值得的谍憔。特別是業(yè)務(wù)變動頻繁帶來的線上hot-fix等高成本的操作,多寫這一部分代碼顯得沒有那么重主籍,另外可以在業(yè)務(wù)穩(wěn)定之后习贫,將struct替換為enum即可,幾乎是無縫切換千元。
另外一個問題就是switch-case
這樣的判斷邏輯上苫昌。我們知道enum中如果每個case都進(jìn)行了區(qū)分,再新增一個case幸海,編譯器就會直接報錯祟身,提示我們有新增的case,記得在switch中進(jìn)行區(qū)分涕烧。
而使用OptionSet之后月而,新增一個case,編譯器并不會為我們進(jìn)行提示议纯,這就需要我們按照業(yè)務(wù)來主動添加了父款,這也無可厚非,畢竟有新業(yè)務(wù)變動了瞻凤,肯定是要涉及到的地方都要修改了憨攒。
使用OptionSet實現(xiàn)enum的邏輯所帶來的問題目前看來都是可以接受的,OptionSet也只是一種用于預(yù)防Codable解析失敗的方式阀参,具體業(yè)務(wù)中最好還是使用enum來實現(xiàn)枚舉功能肝集。
當(dāng)然,如果業(yè)務(wù)變動很頻繁蛛壳,還是推薦使用OptionSet來提升代碼的健壯性的杏瞻。