Swift json字典轉模型 項目記錄

背景

最近項目開始轉用Swift3開發,因爲Swift中json(字典)轉模型的選擇方案較多,筆者最開始選擇了HandyJSON的方案,在使用一段時間後發現當要進行某個字段取值使用時須要進行各類的轉化判斷,比較麻煩(可是安全、保證程序不會拋出異常)。因而筆者引入了SwiftyJSON庫。因而取值變得簡單方便。git

新問題

因爲SwiftyJSON的引入,筆者將網絡請求基本請求完成後進行了JSON化處理,若是後面再進行HandyJSON轉模型處理,就要進行二次操做,感受效率上會有影響。
固然也能夠選擇網絡請求基類對返回數據不作任何處理,交還由各個請求發起者處理,但這樣會致使大量的重複代碼。在這種狀況下筆者思考利用JSON本身進行模型轉化。github

方案

runtime機制給模型賦值

在OC中咱們的json轉模型一般是利用runtime機制獲取模型對象的屬性列表,再進行判斷並取值賦值.因爲Swift中已經逐漸放棄runtime機制。所以筆者放棄了此種方案json

反射機制給模型賦值

在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來進行模型轉化app

模型代碼,保證屬性相同ide

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)
}

測試結果及分析

進行三次測試結果數據以下測試

test-1test-4test-3

有測試數據得出BQModel的效率在這裏比HandyJSON較好。須要注意的是筆者縮寫的模型考慮狀況確定沒有HandyJSON多。因此在通用性方面應該沒有HandyJSON作的好(這裏也多是HandyJSON效率比BQModel低的緣由)。另外HandyJSON採用內存地址寫入,而筆者採用KVC寫入,並不徹底符合Swift的特性。因此在採用哪一種模型方案的時候仍是看你們的需求和選擇。最後附上筆者Swift工具庫鏈接,有興趣的朋友能夠去看看。若是上述有什麼錯誤請指正,謝謝!ui

相關文章
相關標籤/搜索