背景
最近项目开始转用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())>
(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工具库连接,有兴趣的朋友可以去看看。如果上述有什么错误请指正,谢谢!