背景
最近項目開始轉用Swift3開發,由於Swift中json(字典)轉模型的選擇方案較多,筆者最開始選擇了HandyJSON的方案,在使用一段時間后發現當要進行某個字段取值使用時需要進行各種的轉化判斷,比較麻煩(但是安全、保證程序不會拋出異常)。於是筆者引入了SwiftyJSON庫。於是取值變得簡單方便。
新問題
由於SwiftyJSON的引入,筆者將網絡請求基本請求完成后進行了JSON化處理,如果后面再進行HandyJSON轉模型處理,就要進行二次操作,感覺效率上會有影響。
當然也可以選擇網絡請求基類對返回數據不做任何處理,交還由各個請求發起者處理,但這樣會導致大量的重復代碼。在這種情況下筆者思考利用JSON自己進行模型轉化。
方案
runtime機制給模型賦值
在OC中我們的json轉模型通常是利用runtime機制獲取模型對象的屬性列表,再進行判斷並取值賦值.由於Swift中已經逐漸放棄runtime機制。因此筆者放棄了此種方案
反射機制給模型賦值
在Swift中有一個Mirror類,具備獲取對象信息能力、能獲取對象名字及值。接着再通過kvc的方式將值寫入模型即可。(HandyJSON的值寫入是直接通過內存地址寫入)
實現
這里筆者通過反射機制來實現json轉模型(由於項目中使用到了SwiftyJSON,因此添加了JOSN轉化為模型方法)。模型基類代碼如下
import UIKit
import SwiftyJSON
class BQModel: NSObject {
required override init() {
}
required init(_ dic: Dictionary<String,Any>) {
super.init()
self.configValue(dic)
}
func configValue(_ dic: Dictionary<String,Any>) {
let mirror = Mirror(reflecting: self)
for p in mirror.children {
let name = p.label!
if let value = dic[name] {
if p.value is BQModel && value is Dictionary<String, Any> {
(p.value as! BQModel).configValue(value as! Dictionary<String, Any>)
}else {
self.setValue(value, forKey: name)
}
}
}
}
override var description: String {
let mirror = Mirror(reflecting: self)
var result = [String:Any]()
for p in mirror.children {
let name = p.label!
result[name] = p.value
}
return String(describing: "<\(self.classForCoder):\(Unmanaged.passRetained(self).toOpaque())>\n\(result)")
}
// MARK:- ***** if has SWIFTJSON can use this Mesthod *****
required init(_ json: JSON) {
super.init()
self.configValue(json)
}
func configValue(_ json: JSON) {
if let modelInfo = json.dictionary {
let mirror = Mirror(reflecting: self)
for p in mirror.children {
let name = p.label!
if let value = modelInfo[name] {
switch value.type {
case .string:
self.setValue(value.string, forKey: name)
case .number:
self.setValue(value.number, forKey: name)
case .bool:
self.setValue(value.bool, forKey: name)
case .dictionary:
let val = self.value(forKey: name)
if val is BQModel {
(val as! BQModel).configValue(value)
}else {
self.setValue(value.dictionary, forKey: name)
}
case .array:
self.setValue(value.array, forKey: name)
default:
break
}
}
}
}
}
}
extension Array where Element: BQModel {
static func modelArr(arr: Array<Dictionary<String,Any>>) -> Array<Element> {
var result = [Element]()
for dic in arr {
result.append(Element(dic))
}
return result
}
//MARK:- ***** if has SWIFTJSON can use this Mesthod *****
static func modelArr(arr: Array<JSON>) -> Array<Element> {
var result = [Element]()
for json in arr {
result.append(Element(json))
}
return result
}
}
測試
當模型寫好后,筆者進行了一個簡單的測試。將一萬個對象的json字符串作為網絡請求返回值,模擬網絡請求。
然后分別用HandyJSON和BQModel來進行模型轉化
模型代碼,保證屬性相同
class Person: BQModel {
var name: String?
var age: Int = 0
var std = Student()
}
class Student: BQModel {
var id: String?
var num: Int = 0
var isOld: Bool = false
}
struct ABC: HandyJSON {
var name: String?
var age: Int = 0
var std = ABD()
}
struct ABD: HandyJSON {
var id: String?
var num: Int = 0
var isOld: Bool = false
}
測試代碼
//模擬網絡請求返回數據JSON化處理
let json = JSON(["name":"asd","age":11,"std":["id":"123","num":12]])
//數據處理
let arrJSON = [JSON](repeating: json, count: 10000)
let arrDict = [Dictionary](repeating: ["name":"asd","age":11,"std":["id":"123","num":12]], count: 10000)
let string = BQTool.jsonFromObject(obj: arrDict)
//時間測試
print("BQModel with JSON")
self.getTime {
let _ = [Person].modelArr(arr: arrJSON)
}
print("BQModel with Dict")
self.getTime {
et data = string.data(using: .utf8)!
let obj = try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! Array<Dictionary<String,Any>>
let _ = [Person].modelArr(arr: obj)
}
print("HandyJSON")
self.getTime {
let _ = [ABC].deserialize(from: string)
}
測試結果及分析
進行三次測試結果數據如下
有測試數據得出BQModel的效率在這里比HandyJSON較好。需要注意的是筆者縮寫的模型考慮情況肯定沒有HandyJSON多。所以在通用性方面應該沒有HandyJSON做的好(這里也可能是HandyJSON效率比BQModel低的原因)。另外HandyJSON采用內存地址寫入,而筆者采用KVC寫入,並不完全符合Swift的特性。所以在采用哪種模型方案的時候還是看大家的需求和選擇。最后附上筆者Swift工具庫連接,有興趣的朋友可以去看看。如果上述有什么錯誤請指正,謝謝!