使用Runtime给Model类赋值 (前提:字典的Key必须和实体类的Property Name相同)
一、创建我们的测试工程
在本测试工程中使用不到iOS开发的UI部分,所以我们就创建一个基于系统控制台的工程,主调用代码当然是放到main函数中了,Project创建过程如下图所示,Create new project -> OS X -> Application -> Command Line Tool ->一路next即可
二、创建我们的测试数据
1.首先使用for循环创建一个字典,当然字典的key和value在这是有规律的,下面的for循环是创建我们的测试数据,如果在有网络请求的状态下,该测试字典的来源就是你从网络请求的JOSN解析出来的字典,在这儿没有进行网络请求,因为网络请求不是本篇博客的重点,所以就使用for循环生成一个测试字典以供使用。创建测试字典的代码如下,改代码的位置放在main函数当中:
NSMutableDictionary *data = [[NSMutableDictionary alloc] initWithCapacity:11];
//创建测试适用的字典
for(int i = 0; i <= 10; i ++){
NSString *key = [NSString stringWithFormat:@"girl%d", i];
NSString *value = [NSString stringWithFormat:@"我是第%d个女孩", i];
[data setObject:value forKey:key];
}
上述代码生成字典,打印结果如下,可以看出字典是无序的,接下来就将data这个字典作为我们网络请求JSON解析后的字典来使用。
2016-04-22 14:16:43.925 LSRuntimeModel[1836:157420] {
girl0 ="我是第0个女孩";
girl1 ="我是第1个女孩";
girl10 = "我是第10个女孩";
girl2 = "我是第2个女孩";
girl3 = "我是第3个女孩";
girl4 = "我是第4个女孩";
girl5 = "我是第5个女孩";
girl6 = "我是第6个女孩";
girl7 = "我是第7个女孩";
girl8 = "我是第8个女孩";
girl9 = "我是第9个女孩";
}
三、创建data字典对应的实体类
接下来将会创建Data字典对应的实体类,首先将会实现实体类的属性名和字典的key值一致的情况,然后在下篇博客中将会实现如何把不同key值的字典转换成实体类的属性。
1、首先我们先创建一个实体类,这个实体类要继承与实体基类,因为一些公用的方法是在实体基类中进行编写的,如便利构造器,便利初始化方法,把字典转成Model属性等方法回被抽象到这个基类当中。创建实体类如下图所示,创建类的时候选中创建的基类即可:
2、这个实体类的命名为:LSBeautifulGirlModel,下面是LSBeautifulGirlModel中的属性,属性的名字和字典key的值相同,如下所示,LSBaseModelObject是之前创建的基类,LSBaseModelObject继承与NSObject即可。
//
// LSBeautifulGirlModel.h
// LSRuntimeModel
//
// Created by a110 on 16/4/22.
// Copyright © 2016年 a110. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface LSBeautifulGirlModel : NSObject
@property (nonatomic, copy) NSString *girl0;
@property (nonatomic, copy) NSString *girl1;
@property (nonatomic, copy) NSString *girl2;
@property (nonatomic, copy) NSString *girl3;
@property (nonatomic, copy) NSString *girl4;
@property (nonatomic, copy) NSString *girl5;
@property (nonatomic, copy) NSString *girl6;
@property (nonatomic, copy) NSString *girl7;
@property (nonatomic, copy) NSString *girl8;
@property (nonatomic, copy) NSString *girl9;
@property (nonatomic, copy) NSString *girl10;
+ (instancetype)ls_modelWithDictionary: (NSDictionary *) data;
@end
四、实现实体基类中的方法。
实体基类中的方法是从各个Model中抽象出来的并且可以重复利用的部分,在实体基类的方法中大致包括:生成getter方法,生成setter方法,获取Model类的属性,把字典的值赋给对应的Model, 动态的调用getter方法对实体类的属性值进行遍历。本篇博客中回给出一部分,剩下的一部分会在以后的博客中陆续给出。
1.根据Key值生成set方法
首先要编写的方法是传入一个字符串,然后返回该字符串所对应属性的setter方法。这个方法其实很简单的,就是把对应的字符串的首字母大写并且拼接上set关键字,再生产SEL并返回,该方法的具体实现如下:
//
// LSBeautifulGirlModel.m
// LSRuntimeModel
//
// Created by a110 on 16/4/22.
// Copyright © 2016年 a110. All rights reserved.
//
#import "LSBeautifulGirlModel.h"
@implementation LSBeautifulGirlModel
#pragma mark -- 通过字符串来创建该字符串的Setter方法,并返回
- (SEL) creatSetterWithPropertyName: (NSString *) propertyName{
//1.首字母大写
propertyName = propertyName.capitalizedString;
//2.拼接上set关键字
propertyName = [NSString stringWithFormat:@"set%@:", propertyName];
//3.返回set方法
return NSSelectorFromString(propertyName);
}
2.把字典传入到方法中,并把字典的值赋给相应实体类的属性,该方法需要调用上述方法来生成setter方法,通过setter方法把字典的Value赋值给实体类对应的属性,代码如下,下面代码中的注释还是比较详细的,具体细节就参考下面注释的内容了。通过调用这个方法就可以把字典的值赋给对应的实体类的属性,调用这个方法的前提是要求字典的key与实体类的属性名必须相同。有的小伙伴会问如果不一样该怎么做呢?这个问题到下篇博客中在进行介绍。
/************************************************************************
*把字典赋值给当前实体类的属性
*参数:字典
*适用情况:当网络请求的数据的key与实体类的属性相同时可以通过此方法吧字典的Value
* 赋值给实体类的属性
************************************************************************/
-(void) assginToPropertyWithDictionary: (NSDictionary *) data{
if (data == nil) {
return;
}
///1.获取字典的key
NSArray *dicKey = [data allKeys];
///2.循环遍历字典key, 并且动态生成实体类的setter方法,把字典的Value通过setter方法
///赋值给实体类的属性
for (int i = 0; i < dicKey.count; i ++) {
///2.1 通过getSetterSelWithAttibuteName 方法来获取实体类的set方法
SEL setSel = [self creatSetterWithPropertyName:dicKey[i]];
if ([self respondsToSelector:setSel]) {
///2.2 获取字典中key对应的value
NSString *value = [NSString stringWithFormat:@"%@", data[dicKey[i]]];
///2.3 把值通过setter方法赋值给实体类的属性
[self performSelectorOnMainThread:setSel
withObject:value
waitUntilDone:[NSThread isMainThread]];
}
}
}
3.接下来就是开始编写实体类的入口了,也就是便利初始化方法和便利构造器。并在头文件中留出接口,下面先给出便利初始化方法然后在给出便利构造器。
(1)下面的代码是实体类的便利初始化方法,当然是实例方法,该方法需要传入一个字典,这个字典中的key就是该实体类的属性名,值就是要给该实体类的属性赋的值。并且返回该实体类的实例,简单的说就是调用-(void) assginToPropertyWithDictionary: (NSDictionary *) data方法,具体代码如下:
- (instancetype)initWithDictionary: (NSDictionary *) data{
{
self = [super init];
if (self) {
[self assginToPropertyWithDictionary:data];
}
return self;
}
}
(2)下面将要给出便利构造器,当然便利构造器是类方法,并且返回该类的一个实例。用大白话说,便利构造器就是分配内存后调用便利初始化方法然后返回该对象的实例,下方是实体类对应的便利构造器:
+ (instancetype)ls_modelWithDictionary: (NSDictionary *) data{
return [[self alloc] initWithDictionary:data];
}
五、测试上面的代码
LSBeautifulGirlModel *beautifulGirl = [LSBeautifulGirlModel ls_modelWithDictionary:data];
NSLog(@"%@", beautifulGirl.girl0);
输出结果:
2016-04-22 14:24:59.446 LSRuntimeModel[1836:157420] 我是第0个女孩
接下来,实现字典的Key和Model的属性名不一样的情况我们该如何负值
在上一个博客代码基础上在Model基类中添加通过Runtime来遍历Model类的属性值。
一、获取Model的实体属性
1.要想遍历Model类的属性,首先得通过Runtime来获取该Model类有哪些属性,输出Model的所有属性的值可不像遍历Dictionary和Array那样一个for循环搞定的,下面的方法是通过Runtime来获取Model类的属性字符串,并以数组的形式返回。代码如下:
///通过运行时获取当前对象的所有属性的名称,以数组的形式返回
- (NSArray *) ls_allPropertyNames{
///存储所有的属性名称
NSMutableArray *allNames = [[NSMutableArray alloc] init];
///存储属性的个数
unsigned int propertyCount = 0;
///通过运行时获取当前类的属性
objc_property_t *propertys = class_copyPropertyList([self class], &propertyCount);
//把属性放到数组中
for (int i = 0; i < propertyCount; i ++) {
///取出第一个属性
objc_property_t property = propertys[i];
const char * propertyName = property_getName(property);
[allNames addObject:[NSString stringWithUTF8String:propertyName]];
}
///释放
free(propertys);
return allNames;
}
2.获取到Model类的属性方法后需要把属性字符串生成get方法,我们可以执行get方法来获取Model属性的值,下方的方法是根据属性字符串来获取属性的getter方法,OC中属性的getter方法的名字和属性的名字是一致的,生成getter方法比较简单,具体代码如下:
#pragma mark -- 通过字符串来创建该字符串的Setter方法,并返回
- (SEL) ls_creatGetterWithPropertyName: (NSString *) propertyName{
//1.返回get方法: oc中的get方法就是属性的本身
return NSSelectorFromString(propertyName);
}
二、Get方法的执行
接下来要做的是通过Runtime来执行Getter方法,这一块需要通过方法的签名来执行Getter方法。在OC的运行时中要执行的方法需要传入参数或者需要接收返回值时,需要通过方法的签名来调用方法。下面的代码就是创建方法的签名,然后通过签名来获取调用的对象,在下边的方中回调用上述两个方法在通过方法的签名来获取Model属性的值,具体代码如下:
///通过运行时获取当前对象的所有属性的名称,以数组的形式返回
const char *propertiesKey = "propertiesKey";
- (NSArray *) ls_allPropertyNames{
//参数一 关联到对象
//参数二 关联的属性key
//在oc 中 类的本质就是一个对象 将属性列表缓存
NSArray *plist = objc_getAssociatedObject(self, propertiesKey);
if(plist != nil)
{
return plist;
}
///存储所有的属性名称
NSMutableArray *allNames = [[NSMutableArray alloc] init];
///存储属性的个数
unsigned int propertyCount = 0;
///通过运行时获取当前类的属性
objc_property_t *propertys = class_copyPropertyList([self class], &propertyCount);
//把属性放到数组中
for (int i = 0; i < propertyCount; i ++) {
///取出第一个属性
objc_property_t property = propertys[i];
const char * propertyName = property_getName(property);
[allNames addObject:[NSString stringWithUTF8String:propertyName]];
}
///释放
free(propertys);
//5 设置关联对象
//参数1>关联的对象
//参数2>关联对象的key
//参数3>属性数值
//属性的持有方式 retain copy assign
objc_setAssociatedObject(self, propertiesKey, allNames, OBJC_ASSOCIATION_COPY_NONATOMIC);
NSLog(@"%@",allNames);
return allNames;
}
执行上述方法就可以输入Model中的属性的值,下面就在main函数中对Model赋完值后调用上述方法输出一下Model的属性值,调用代码如下所示:
LSBeautifulGirlModel *beautifulGirl = [LSBeautifulGirlModel ls_modelWithDictionary:data];
[beautifulGirl ls_displayCurrentModleProperty];
运行结果如下,下面的输出结果是Model中属性的值。
2016-04-22 14:41:08.568 LSRuntimeModel[1898:172805] 我是第0个女孩
我是第1个女孩
我是第2个女孩
我是第3个女孩
我是第4个女孩
我是第5个女孩
我是第6个女孩
我是第7个女孩
我是第8个女孩
我是第9个女孩
我是第10个女孩
三、Dictionary的Key与Model的属性不同的处理方式
有时候会遇到字典的key和Model的属性不一样的情况,那么如何去解决这个问题呢?最简单的做法是在具体的实体类中去维护一个映射关系方法,通过这个方法我们可以获取相应的的映射关系。
1.在Model的基类中添加一个返回映射字典的一个方法,然后在子类中进行重写,这个映射方法在基类中返回nil, 如果子类需要重写的话就对这个方法进行重写并返回映射字典。方法如下:
#pragma 返回属性和字典key的映射关系
-(NSDictionary *) ls_propertyMapDic{
return nil;
}
2.修改一下我们的便利初始化方法,在有映射字典的情况和没有映射字典的情况下调用的方法是不一样的,便利初始化方法的代码如下:
- (instancetype)initWithDictionary: (NSDictionary *) data{
{
self = [super init];
if (self) {
if ([self ls_propertyMapDic] == nil) {
[self ls_assginToPropertyWithDictionary:data];
} else {
[self ls_assginToPropertyWithNoMapDictionary:data];
}
}
return self;
}
}
3.接下来就将实现有映射关系要调用的方法,这个方法就是通过映射关系把字典的key转换成与property的名字一样的字典,然后调用之前的赋值方法,具体代码如下:
#pragma 根据映射关系来给Model的属性赋值
-(void) ls_assginToPropertyWithNoMapDictionary: (NSDictionary *) data{
///获取字典和Model属性的映射关系
NSDictionary *propertyMapDic = [self ls_propertyMapDic];
///转化成key和property一样的字典,然后调用assginToPropertyWithDictionary方法
NSArray *dicKey = [data allKeys];
NSMutableDictionary *tempDic = [[NSMutableDictionary alloc] initWithCapacity:dicKey.count];
for (int i = 0; i < dicKey.count; i ++) {
NSString *key = dicKey[i];
[tempDic setObject:data[key] forKey:propertyMapDic[key]];
}
[self ls_assginToPropertyWithDictionary:tempDic];
}
4.创建一个BadBoyModel, 并重写propertyMapDic方法,并且在propertyMapDic方法中给出映射关系并返回该映射关系对应的字典。
(1)BadBoyModel的属性如下:
//
// LSBadBoyModel.h
// LSRuntimeModel
//
// Created by a110 on 16/4/22.
// Copyright © 2016年 a110. All rights reserved.
//
#import "LSBaseModelObject.h"
@interface LSBadBoyModel : LSBaseModelObject
@property (nonatomic, copy) NSString *boy1;
@property (nonatomic, copy) NSString *boy2;
@property (nonatomic, copy) NSString *boy3;
@property (nonatomic, copy) NSString *boy4;
@end
(2)重写映射方法,映射字典的key是要转换字典的key, Value是对应Model的属性名。
//
// LSBadBoyModel.m
// LSRuntimeModel
//
// Created by a110 on 16/4/22.
// Copyright © 2016年 a110. All rights reserved.
//
#import "LSBadBoyModel.h"
@implementation LSBadBoyModel
-(NSDictionary *) ls_propertyMapDic{
return @{@"keyBoy1":@"boy1",
@"keyBoy2":@"boy2",
@"keyBoy3":@"boy3",
@"keyBoy4":@"boy4",};
}
@end
5.在main函数中进行测试
(1)、生成我们的数值字典,字典的key与要赋值Model的属性不同,下面的循环就是要生成测试使用的数据:
//生成Dic的Key与Model的属性不一样的字典。
NSMutableDictionary *data1 = [[NSMutableDictionary alloc] init];
//创建测试适用的字典
for(int i = 1; i <= 4; i ++){
NSString *key = [NSString stringWithFormat:@"keyBoy%d", i];
NSString *value = [NSString stringWithFormat:@"我是第%d个坏男孩", i];
[data1 setObject:value forKey:key];
}
(2) 实例化Model并输出结果,当然之前的代码也是可以使用的。
LSBadBoyModel *badBoyModel = [LSBadBoyModel ls_modelWithDictionary:data1];
[badBoyModel ls_displayCurrentModleProperty];
运行结果如下:
2016-04-22 14:58:30.608 LSRuntimeModel[1959:184512] 我是第1个坏男孩
我是第2个坏男孩
我是第3个坏男孩
我是第4个坏男孩