很多时候我们需要把从服务器端请求下来的数据转成 model 类,今天就介绍一下如何利用 runtime 实现字典转模型
###1、首先先建一个 model 类
1 | class Person:NSObject { |
2 | var name:String? |
3 | var age:NSNumber? |
4 | } |
###2、为 NSObject 创建一个 extension,在其中实现字典转模型
主要分为一下几步 ####(1)获取所有的属性的名字 ####(2)通过属性的名字去字典里取值 ####(3)用 KVC 为 model 类赋值
1 | extension NSObject{ |
2 | class func objectWithKeyValues(keyValues:NSDictionary) -> AnyObject{ |
3 | let model = self.init() |
4 | //存放属性的个数 |
5 | var outCount:UInt32 = 0 |
6 | //获取所有的属性 |
7 | let properties = class_copyPropertyList(self.classForCoder(), &outCount) |
8 | //遍历属性 |
9 | for var i = 0;i < Int(outCount);i++ { |
10 | //获取第i个属性 |
11 | let property = properties[i] |
12 | //得到属性的名字 |
13 | let key = NSString(CString: property_getName(property), encoding: NSUTF8StringEncoding)! |
14 | if let value = keyValues[key]{ |
15 | //为model类赋值 |
16 | model.setValue(value, forKey: key as String) |
17 | } |
18 | } |
19 | return model |
20 | } |
21 | } |
###3、测试结果
但是有时候我们经常会遇到这样的情况 model 类继承自父类,比如
1 | class Person:NSObject { |
2 | var name:String? |
3 | var age:NSNumber? |
4 | } |
5 | class Student:Person{ |
6 | var number:String? |
7 | var score:NSNumber? |
8 | } |
Student 类继承自 Person 类我们也要为 Student 类中父类的属性赋值,也就是说我们在遍历属性的同时也要为父类的属性
###4、遍历父类中得属性
在这种情况下我改变了一下思路,在获取属性的同时不在用 KVC 直接赋值,而是把获取的属性存起来,得到所有的属性之后在进行赋值
把扩展中得代码改造之后如下
1 | extension NSObject{ |
2 | class func objectWithKeyValues(keyValues:NSDictionary) -> AnyObject{ |
3 | let model = self.init() |
4 | //获取所有的属性 |
5 | let properties = self.allProperties() |
6 | if let _ = properties{ |
7 | for property in properties!{ |
8 | if let value = keyValues[property.propertyNmae]{ |
9 | //为model类赋值 |
10 | model.setValue(value, forKey: property.propertyNmae as String) |
11 | } |
12 | } |
13 | } |
14 | return model |
15 | } |
16 | class func allProperties() -> [LKKProperty]?{ |
17 | let className = NSString(CString: class_getName(self), encoding: NSUTF8StringEncoding) |
18 | if let _ = NSString(CString: class_getName(self), encoding: NSUTF8StringEncoding){ |
19 | //不用为NSObject的属性赋值 |
20 | if className!.isEqualToString("NSObject"){ |
21 | return nil |
22 | } |
23 | }else{ |
24 | return nil |
25 | } |
26 | var outCount:UInt32 = 0 |
27 | //所有属性LKKProperty里面放着存放这个属性 |
28 | var propertiesArray = [LKKProperty]() |
29 | let properties = class_copyPropertyList(self.classForCoder(),&outCount) |
30 | //获取父类的所有属性 |
31 | let superM = self.superclass()?.allProperties() |
32 | if let _ = superM{ |
33 | //把父类中得所有属性添加进去 |
34 | propertiesArray += superM! |
35 | } |
36 | //遍历自己的属性添加进去 |
37 | for var i = 0;i < Int(outCount);i++ { |
38 | let property = LKKProperty(property: properties[i]) |
39 | propertiesArray.append(property) |
40 | } |
41 | return propertiesArray |
42 | } |
43 | } |
44 | class LKKProperty{ |
45 | var propertyNmae:NSString! |
46 | var property:objc_property_t |
47 | init(property:objc_property_t){ |
48 | self.property = property |
49 | self.propertyNmae = NSString(CString: property_getName(property), encoding: NSUTF8StringEncoding) |
50 | } |
51 | } |
###5、我们还经常遇到这种情况,类型嵌套,比如 Person 类里面还有一个 Card 类代表着我们的身份信息,我们在赋值的时候也想直接把 Card 的所有属性都能直接弄好,也就是说我们在遍历属性
思路如下,在获取属性的过程中判断是否属于 Foundtation 框架,如果是直接赋值,如果不是就获取这个类的所有属性,对这个类的属性进行赋值,然后把 Card 这个类的对象赋值给 Person 这个类的对象,修改代码如下。
1 | class LKKProperty{ |
2 | //属性名字 |
3 | var propertyNmae:NSString! |
4 | //属性 |
5 | var property:objc_property_t |
6 | //属性类型 |
7 | var propertyType:LKKType! |
8 | init(property:objc_property_t){ |
9 | self.property = property |
10 | self.propertyNmae = NSString(CString: property_getName(property), encoding: NSUTF8StringEncoding) |
11 | //自定义的类的描述格式为T@"_TtC15字典转模型4Card",N,&,Vcard |
12 | //T+@+"+..+工程的名字+数字+类名+"+,+其他,而我们想要的只是类名,所以要修改这个字符串 |
13 | //获取类的描述 |
14 | var code: NSString = NSString(CString: property_getAttributes(property), encoding: NSUTF8StringEncoding)! |
15 | //直接取出""中间的内容 |
16 | code = code.componentsSeparatedByString("\"")[1] |
17 | let bundlePath = getBundleName() |
18 | let range = code.rangeOfString(bundlePath) |
19 | if range.length > 0{ |
20 | //去掉工程名字之前的内容 |
21 | code = code.substringFromIndex(range.length + range.location) |
22 | } |
23 | //在去掉剩下的数字 |
24 | var number:String = "" |
25 | for char in (code as String).characters{ |
26 | if char <= "9" && char >= "0"{ |
27 | number += String(char) |
28 | }else{ |
29 | break |
30 | } |
31 | } |
32 | let numberRange = code.rangeOfString(number) |
33 | if numberRange.length > 0{ |
34 | //获取类名 |
35 | code = code.substringFromIndex(numberRange.length + numberRange.location) |
36 | } |
37 | //得到类的Type |
38 | self.propertyType = LKKType(code: code) |
39 | } |
40 | } |
41 | class LKKType { |
42 | //类名 |
43 | var code:NSString |
44 | //class |
45 | var typeClass:AnyClass? |
46 | //是否属于Foundtation |
47 | var isFromFoundtion:Bool = true |
48 | init(code:NSString){ |
49 | self.code = code |
50 | //判断是否属于Foundtation框架 |
51 | if self.code.hasPrefix("NS"){ |
52 | self.typeClass = NSClassFromString(self.code as String) |
53 | self.isFromFoundtion = true |
54 | }else{ |
55 | //如果是自定义的类NSClassFromString这个方法传得字符串是工程的名字+类名 |
56 | self.typeClass = getClassWitnClassNmae(self.code as String) |
57 | self.isFromFoundtion = false |
58 | } |
59 | } |
60 | } |
61 | //获取工程的名字 |
62 | func getBundleName() -> String{ |
63 | var bundlePath = NSBundle.mainBundle().bundlePath |
64 | bundlePath = bundlePath.componentsSeparatedByString("/").last! |
65 | bundlePath = bundlePath.componentsSeparatedByString(".").first! |
66 | return bundlePath |
67 | } |
68 | //通过类名返回一个AnyClass |
69 | func getClassWitnClassNmae(name:String) ->AnyClass?{ |
70 | let type = getBundleName() + "." + name |
71 | return NSClassFromString(type) |
72 | } |
###6、未完成的事情 ####(1)当类的属性与字典里的 key 值不一定的时候,出现的情况:字典里面的 key 是关键字的时候 ####(2)当类的属性是数组,并且数组里面要放自定义类的时候
接着完成未完成的事情,首先当字典里的 key 值与属性不一致的时候,我弄了个映射 ####一、解决类的属性与字典里的 key 值不一定的情况
1 | //如果需要映射关系,就让子类复写此方法,获取映射到得值 |
2 | //并且在LKKProperty这个类中添加key属性,用来取的字典Key |
3 | func replacedKeyFromPropertyName() -> NSDictionary{ |
4 | return ["属性名字":"key名字"] |
5 | } |
####二、首先我们添加一个方法,这个方法的作用是把字典数组转成模型数组。代码如下
1 | //把一个字典数组转成一个模型数组 |
2 | class func objectArrayWithKeyValuesArray(array:NSArray) -> [AnyObject]{ |
3 | var temp = Array<AnyObject>() |
4 | let properties = self.allProperties() |
5 | for(var i = 0;i < array.count;i++){ |
6 | let keyValues = array[i] as? NSDictionary |
7 | if (keyValues != nil){ |
8 | let model = self.init() |
9 | //为每个model赋值 |
10 | model.setValuesForProperties(properties, keyValues: keyValues!) |
11 | temp.append(model) |
12 | } |
13 | } |
14 | return temp |
15 | } |
三,当判断类的属性是数组的时候,通过这个方法拿到数组里面的类型
1 | //子类重写这个方法,说明数组里存放的对象类型 |
2 | func objectClassInArray() -> [String:String]{ |
3 | return ["属性名":"自定义类名"] |
4 | } |
四、在赋值时若发现是数组并且是数组里装的是自定义类的时候,用二的方法得到数组对象,并且赋值
1 | //把一个字典里的值赋给一个对象的值 |
2 | func setValuesForProperties(properties:[LKKProperty]?,keyValues:NSDictionary){ |
3 | //判断属性数组是否存在 |
4 | if let _ = properties{ |
5 | for property in properties!{ |
6 | //判断该属性是否属于Foundtation框架 |
7 | if property.propertyType.isFromFoundtion { |
8 | if let value = keyValues[property.key]{ |
9 | //判断是否是数组,若是数组,判断数组里装的类是否是自定义类 |
10 | if property.propertyType.isArray && property.propertyType.arrayClass != nil && value is NSArray{ |
11 | //把字典数组转换成模型数组 |
12 | let temp = property.propertyType.arrayClass!.objectArrayWithKeyValuesArray(value as! NSArray) |
13 | //为model类赋值 |
14 | self.setValue(temp, forKey: property.propertyNmae as String) |
15 | }else{ |
16 | //为model类赋值 |
17 | self.setValue(value, forKey: property.propertyNmae as String) |
18 | } |
19 | } |
20 | }else{ |
21 | if let value = keyValues[property.key]{ |
22 | if value is NSDictionary{ |
23 | let subClass = property.propertyType.typeClass?.objectWithKeyValues(value as! NSDictionary) |
24 | //为model类赋值 |
25 | self.setValue(subClass, forKey: property.propertyNmae as String) |
26 | } |
27 | } |
28 | } |
29 | } |
30 | } |
31 | } |
这些内容下篇文章见,demo 下载地址
