一般的json数据转模型都是用的KVC,用数据中的值作为key去匹配模型,通过
setValue(value:, forKey: )
方法来生成模型数据,但是这样的方法有一个很大的弊端就是如果数据中的key在模型中不存在那么就会报错,所以要解决这个问题就应该反过来,通过模型中的属性去数据中查找,这里就需要用到runtime,
首先定义一些关系复杂model类
class Person: NSObject {
var name:String?
var age:NSNumber?
var idCard:Card?
var persons:[Person]?
}
class Student: Person {
var Class:Int?
}
class Card: NSObject {
var id:String?
}
以上模型类需要解决的问题是,Student继承自Person,persons是个数组,类型是Person,idCard是另一个类,基本上平时会碰到的数据结构大概就是这样,层次可能更多,但是类型基本不会出这几种。
要实现以上model的数据转换,可以为NSObject创建一个extension,在其中实现字典转模型
思路是这样的:
1.用runtime获取模型的所有属性名称,存放在一个数组中
2.获取到的属性可能除了一些基本属性以外还会有特殊的,例如数组,自定义类,自定义类的子类等,自定义类型的数组等等等等,所以存放属性名称的数组最好是自定义的一个类型的属性,
首先,自定义一个类来存储获取到的属性名称,以及一些关于属性名称的判断,
/// 自定义的获取到的属性类
class CHProperty{
//属性名字
var propertyNmae:NSString!
//属性名字对应的key
var key:String!
//属性
var property:objc_property_t
//属性类型
var propertyType:CHType!
init(property:objc_property_t){
self.property = property
self.propertyNmae = NSString(cString: property_getName(property), encoding: String.Encoding.utf8.rawValue)
key = self.propertyNmae as String
//自定义的类的Types格式为T@"_TtC15字典转模型4Card",N,&,Vcard
//T+@+"+..+工程的名字+数字+类名+"+,+其他,而我们想要的只是类名,所以要修改这个字符串
var code: NSString = NSString(cString: property_getAttributes(property), encoding: String.Encoding.utf8.rawValue)!
//直接取出""中间的内容
code = code.components(separatedBy: "\"")[1] as NSString
let bundlePath = getBundleName()
let range = code.range(of: bundlePath)
if range.length > 0{
//去掉工程名字之前的内容
code = code.substring(from: range.length + range.location) as NSString
}
//在去掉剩下的数字
var number:String = ""
for char in (code as String).characters{
if char <= "9" && char >= "0"{
number += String(char)
}else{
break
}
}
let numberRange = code.range(of: number)
if numberRange.length > 0{
//得到类名
code = code.substring(from: numberRange.length + numberRange.location) as NSString
}
self.propertyType = CHType(code: code)
}
}
class CHType {
//类名字
var code:NSString
//类的类型
var typeClass:AnyClass?
//是否属于Foundtation框架
var isFromFoundtion:Bool = true
//是否是数组
var isArray:Bool = false
//数组里面存放的类型
var arrayClass:AnyClass?
init(code:NSString){
self.code = code
//判断是否属于Foundtation框架
if self.code.hasPrefix("NS"){
self.typeClass = NSClassFromString(self.code as String)
self.isFromFoundtion = true
if self.code.hasPrefix("NSArray"){
self.isArray = true
}
}else{
//如果是自定义的类NSClassFromString这个方法传得字符串是工程的名字+类名
self.typeClass = getClassWith(self.code as String)
self.isFromFoundtion = false
}
}
}
通过CHType类可以通过类型名来获取类是否是Foundtation类,是否是数组,如果是数组,可以知道数组中的是什么类型,
CHProperty类可以存储属性名字,通过获取底层字符串可以获取到包含类名的一串字符串,通过字符串的处理可以得到类名
//获取工程的名字
func getBundleName() -> String{
var bundlePath = Bundle.main.bundlePath
bundlePath = bundlePath.components(separatedBy: "/").last!
bundlePath = bundlePath.components(separatedBy: ".").first!
return bundlePath
}
//通过类名返回一个AnyClass
func getClassWith(_ className:String) ->AnyClass?{
let type = getBundleName() + "." + className
return NSClassFromString(type)
}
开始将一个数据转换成模型
//获取模型 ,数据转模型方法的入口
class func createModelWith(_ dictionary:NSDictionary) -> AnyObject{
let model = self.init()
//获取所有的属性
let properties = self.getProperties()
model.setValue(dictionary, forKeys: properties)
return model
}
主要是封装的
setValue(_ values:, forKeys properties:)
方法
//把一个字典里的值赋给一个对象的值
func setValue(_ values:NSDictionary, forKeys properties:[CHProperty]?){
//判断属性数组是否存在
if let _ = properties{
for property in properties!{
//判断该属性是否属于Foundtation框架
if property.propertyType.isFromFoundtion {
if let value = values[property.key]{
//判断是否是数组,若是数组,判断数组里装的类是否是自定义类
if property.propertyType.isArray && property.propertyType.arrayClass != nil && value is NSArray{
//把字典数组转换成模型数组
let temp = property.propertyType.arrayClass!.convert(value as! NSArray)
//为model类赋值
print("\(property.propertyNmae!):\(temp)")
self.setValue(temp, forKey: property.propertyNmae as String)
}else{
//为model类赋值
print("\(property.propertyNmae!):\(value)")
self.setValue(value, forKey: property.propertyNmae as String)
}
}
}else{
if let value = values[property.key]{
if value is NSDictionary{
let subClass = property.propertyType.typeClass?.createModelWith(value as! NSDictionary)
//为model类赋值
self.setValue(subClass, forKey: property.propertyNmae as String)
}
}
}
}
}
}
中间碰到是数组的数据类型使用
//把一个字典数组转成一个模型数组
class func convert(_ array:NSArray) -> [AnyObject]{
var temp = Array<AnyObject>()
let properties = self.getProperties()
for i in 0 ..< array.count {
let keyValues = array[i] as? NSDictionary
if (keyValues != nil){
let model = self.init()
//为每个model赋值
model.setValue(keyValues!, forKeys: properties)
temp.append(model)
}
}
return temp
}
解决数组的问题
特别注意的是如果有数组,或者model的属性跟关键字冲突的时候可以重写
//子类重写这个方法,对字典里的key和类的属性进行映射
func replacedKeyFromPropertyName() ->[String:String]{
return ["":""]
}
//子类重写这个方法,说明数组里存放的数据类型
func objectClassInArray() -> [String:String]{
return ["":""]
}
这两个方法来解决