一.自动生成属性的分类
模型属性,通常需要跟字典中的key一一对应。从服务器得到的数据太杂?数据太多?写成plist文件后一个个对照填写属性,太繁琐?那么我么可以尝试写一个分类来自动打印出所有属性。
• 需求:能不能根据一个字典,自动生成对应的属性。
• 解决:提供一个分类,专门根据字典生成对应的属性字符串。


1 #import <Foundation/Foundation.h>
2
3 @interface NSDictionary (PropertyCode)
4
5 // 生成属性代码
6 - (void)createPropetyCode;
7 @end
8
9 @implementation NSDictionary (PropertyCode)
10 // 私有API:真实存在,但是苹果没有暴露出来,不给你用
11 // isKindOfClass:判断下是否是当前类或者子类
12 // 自动生成属性代码
13 - (void)createPropetyCode
14 {
15 NSMutableString *codes = [NSMutableString string];
16 // 遍历字典
17 [self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, BOOL * _Nonnull stop) {
18 NSString *code = nil;
19
20 if ([value isKindOfClass:[NSString class]]) {// 注:NSString *笔者喜欢用strong,若想使用copy可修改字符串
21 code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;",key];
22 } else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
23 code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",key];
24 } else if ([value isKindOfClass:[NSNumber class]]) {
25 code = [NSString stringWithFormat:@"@property (nonatomic, assign) NSInteger %@;",key];
26 } else if ([value isKindOfClass:[NSArray class]]) {
27 code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",key];
28 } else if ([value isKindOfClass:[NSDictionary class]]) {
29 code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",key];
30 }
31
32 // 拼接字符串
33 [codes appendFormat:@"\n%@\n",code];
34
35 }];
36
37 NSLog(@"%@",codes);
38 }
39 @end分类NSDictionary+PropertyCode
外界使用:


1 #import "ViewController.h"
2 #import "Status.h"
3 #import "NSDictionary+PropertyCode.h"
4 /*
5 plist:
6 字典
7 字典转模型
8 */
9
10 @interface ViewController ()
11
12 @end
13
14 @implementation ViewController
15
16 - (void)viewDidLoad {
17 [super viewDidLoad];
18
19 // 解析Plist
20 // 获取文件全路径
21 NSString *fileName = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
22 // 获取字典
23 NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:fileName];
24
25 // 设计模型-定义属性
26 // 自动生成属性代码
27 [dict createPropetyCode];
28 }
29 @endViewController
status.plist

控制台打印信息:


2016-04-15 10:43:34.959 Runtime(自动生成属性)[38837:1398902]
@property (nonatomic, assign) BOOL aaa;
@property (nonatomic, assign) NSInteger reposts_count;
@property (nonatomic, copy) NSString *source;
@property (nonatomic, strong) NSArray *pic_urls;
@property (nonatomic, copy) NSString *created_at;
@property (nonatomic, assign) NSInteger attitudes_count;
@property (nonatomic, copy) NSString *idstr;
@property (nonatomic, copy) NSString *text;
@property (nonatomic, assign) NSInteger comments_count;
@property (nonatomic, strong) NSDictionary *user;控制台打印信息
二.字典转模型KVC实现
KVC必须要保证:模型中属性名要跟字典中key一一对应。
需求:开发中,通常后台会给你很多数据,但是并不是每个数据都有用,这些没有用的数据,需不需要保存到模型中?
使用KVC把字典转成模型:


#import <Foundation/Foundation.h>
@interface Status : NSObject
// 字典中有多少key,模型就有多少个属性
// 自动生成属性 -> 依赖字典
@property (nonatomic, assign) BOOL aaa;
@property (nonatomic, strong) NSString *source;
@property (nonatomic, assign) NSInteger reposts_count;
@property (nonatomic, strong) NSArray *pic_urls;
@property (nonatomic, strong) NSString *created_at;
@property (nonatomic, assign) NSInteger attitudes_count;
@property (nonatomic, strong) NSString *idstr;
@property (nonatomic, strong) NSString *text;
@property (nonatomic, assign) NSInteger comments_count;
@property (nonatomic, strong) NSDictionary *user;
// 定义属性 -> 设计模型
+ (instancetype)statusWithDict:(NSDictionary *)dict;
@end
@implementation Status
+ (instancetype)statusWithDict:(NSDictionary *)dict{
// 创建模型
Status *s = [[self alloc] init];
// 字典value转模型属性保存
[s setValuesForKeysWithDictionary:dict];
return s;
}
@endStatus模型
KVC的底层实现:
setValuesForKeysWithDictionary:遍历字典中所有key,去模型中查找对应的属性,把值给模型属性赋值。


//[s setValuesForKeysWithDictionary:dict];底层实现:
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
[s setValue:obj forKey:key];
}];setValuesForKeysWithDictionary:底层实现
[s setValue:dict[@"source"] forKey:@"source"];
原理:
1.首先会去模型中查找有没有【setSource方法】,如果有 直接调用set方法 [s setSource:dict[@"source"]];
2.否则,去模型中查找有没有【source属性】,如果有 source = dict[@"source"]
3.否则,去模型中查找有没有【_source属性】,如果有 _source = dict[@"source"]
4.再否则,调用对象的 【setValue:forUndefinedKey:】直接报错
使用KVC最大的弊端:模型中属性名要跟字典中key一一对应,不对应就会报错:
setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key source.'
那么,【重写setValue:forUndefinedKey:方法】,屏蔽系统报错就能解决模型中属性名要跟字典中key必须对应的问题。
@implementation Status
+ (instancetype)statusWithDict:(NSDictionary *)dict{
// 创建模型
Status *s = [[self alloc] init];
// 字典value转模型属性保存
[s setValuesForKeysWithDictionary:dict];
return s;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {}
@end这样就不用把字典中的所有key都写入模型了,只需要写我们想要的数据。
三.Runtime字典转模型一级转换
刚刚介绍了字典转模型的第一种方式---KVC,下面进入主题。
• 字典转模型的方式二:Runtime
◦ 思路:利用运行时,【遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值】。
◦ 步骤:提供一个NSObject分类,专门字典转模型,以后所有模型都可以通过这个分类来转。


1 #import <Foundation/Foundation.h>
2
3 @interface NSObject (Model)
4
5 + (instancetype)modelWithDict:(NSDictionary *)dict;
6
7 @end
8
9
10 #import <objc/message.h>
11
12 //class_copyPropertyList(__unsafe_unretained Class cls, unsigned int *outCount)
13 //这个方法只能获取【属性】列表(大括号里的成员变量不能获取)
14
15 @implementation NSObject (Model)
16 + (instancetype)modelWithDict:(NSDictionary *)dict
17 {
18 id objc = [[self alloc] init];
19
20 // 获取成员变量列表
21 // 第一个参数class:获取哪个类成员变量列表
22 // 第二个参数count:成员变量总数
23 unsigned int count = 0;
24
25 // 成员变量数组 指向数组第0个元素
26 Ivar *ivarList = class_copyIvarList(self, &count);
27
28 // 遍历所有成员变量
29 for (int i = 0; i < count; i++) {
30 // 获取成员变量
31 Ivar ivar = ivarList[i];
32 // 获取成员变量名称
33 NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
34
35 // 成员变量名称转换key,去掉成员变量名称前面的下划线
36 NSString *key = [ivarName substringFromIndex:1];
37
38 // 从字典中取出对应value
39 id value = dict[key];
40
41 // 给模型中属性赋值
42 [objc setValue:value forKey:key];
43 }
44 return objc;
45 }
46 @endNSObject+Model分类
模型中只需要保留想要的属性名即可。
四.Runtime字典转模型二级转换
开发中可能会遇到,二级转换:如果字典中还有字典,也需要把对应的字典转换成模型,那么就需要二级转换。

1 #import <Foundation/Foundation.h>
2
3 @interface NSObject (Model)
4
5 + (instancetype)modelWithDict:(NSDictionary *)dict;
6
7 @end
8
9 #import <objc/message.h>
10
11 @implementation NSObject (Model)
12 + (instancetype)modelWithDict:(NSDictionary *)dict
13 {
14 id objc = [[self alloc] init];
15
16 unsigned int count = 0;
17
18 // 成员变量数组 指向数组第0个元素
19 Ivar *ivarList = class_copyIvarList(self, &count);
20
21 // 遍历所有成员变量
22 for (int i = 0; i < count; i++) {
23
24 // 获取成员变量 user
25 Ivar ivar = ivarList[i];
26 // 获取成员变量名称
27 NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
28
29 // 获取成员变量类型(后续判断是否进行二次转换)
30 NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
31 NSLog(@"%@",type);
32 // @"@"User"" -> @"User"
33 type = [type stringByReplacingOccurrencesOfString:@"@\"" withString:@""];
34 type = [type stringByReplacingOccurrencesOfString:@"\"" withString:@""];
35
36 // 成员变量名称转换key
37 NSString *key = [ivarName substringFromIndex:1];
38
39 // 从字典中取出对应value dict[@"user"] -> 字典
40 id value = dict[key];
41
42 // 二级转换
43 // 并且是自定义类型,才需要转换
44 if ([value isKindOfClass:[NSDictionary class]] && ![type containsString:@"NS"]) { // 只有是字典,并且不包含NS才需要转换
45
46 //获取类名
47 Class className = NSClassFromString(type);
48
49 // 字典转模型
50 value = [className modelWithDict:value];
51 }
52
53 // 给模型中属性赋值 key:user value:字典 ---> 模型
54 if (value) {
55 [objc setValue:value forKey:key];
56 }
57 }
58 return objc;
59 }
60 @end
















