iOS开发之数据转模型(runtime)

一般的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 ["":""]
    }

这两个方法来解决

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值