在iOS开发过程中,经常遇到将字典数据转换成model的情况,网上也有很多数据类型转换的框架,像JSONModel、MJExtension等,功能强大,有时间拜读拜读源码
在维护项目过程中,由于历史问题,并没有使用模型转换框架,每个数据模型都需要手动将字典转化为数据模型,每个模型中都产生了大量的相同的代码,就像下面这样:
觉得这样做很傻,重复造轮子,就想着优化方案,花了两三个小时,实现了基本的需求,便在这里记录一下
基本思路:
在NSObject类中新增初始化方法,将字典转换为模型对象的属性;
这样势必要得到对象的成员变量,我们可以使用运行时来得到对象的成员变量,使用KVC方式将字典中各个字段赋值给对应的属性;
针对字典的key不同于成员变量的问题,可以传递一个映射字典来解决
实现如下:
步骤一:
在NSObject中新增初始化方法
NSObject.h 文件中
#import
@interface NSObject (InitData)
/**
模型初始化
@param ModelDic 模型字典
@param hintDic 映射字典,如果不需要则nil
@return 模型对象
*/
+(instancetype)objectWithModelDic:(NSDictionary*)modelDic hintDic:(NSDictionary*)hintDic;
@end
.m 文件实现部分
#import "NSObject+InitData.h"
#import
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
@implementation NSObject (InitData)
+(instancetype)objectWithModelDic:(NSDictionary *)modelDic hintDic:(NSDictionary *)hintDic{
NSObject *instance = [[[self class] alloc] init];
unsigned int numIvars; // 成员变量个数
Ivar *vars = class_copyIvarList([self class], &numIvars);
NSString *key=nil;
NSString *key_property = nil; // 属性
NSString *type = nil;
for(int i = 0; i < numIvars; i++) {
Ivar thisIvar = vars[i];
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)]; // 获取成员变量的名字
key = [key hasPrefix:@"_"]?[key substringFromIndex:1]:key; // 如果是属性自动产生的成员变量,去掉头部下划线
key_property = key;
// 映射字典,转换key
if (hintDic) {
key = [hintDic objectForKey:key]?[hintDic objectForKey:key]:key;
}
id value = [modelDic objectForKey:key];
if (value==nil) {
type = [NSString stringWithUTF8String:ivar_getTypeEncoding(thisIvar)]; //获取成员变量的数据类型
// 列举了常用的基本数据类型,如果有其他的,需要添加
if ([type isEqualToString:@"B"]||[type isEqualToString:@"d"]||[type isEqualToString:@"i"]|[type isEqualToString:@"I"]||[type isEqualToString:@"f"]||[type isEqualToString:@"q"]) {
value = @(0);
}
}
[instance setValue:value forKey:key_property];
}
free(vars);
return instance;
}
@end
这样,所有数据模型都可以直接调用这个方法完成字典转模型操作了,而不用在单独为每个模型编写初始化方法
简单使用:
新建Person类
#import
#import
typedef NS_ENUM(NSInteger , Sex) {
Male,
Female
};
@interface Person : NSObject
@property (copy ,nonatomic) NSString *name;
@property (assign ,nonatomic) Sex sex;
@property (assign ,nonatomic) int age;
@property (assign ,nonatomic) CGFloat height;
@property (assign ,nonatomic) float money;
@property (copy ,nonatomic) NSDictionary *otherInfo;
@property (assign ,nonatomic) BOOL isHandsome;
@property (copy ,nonatomic) NSArray *familyMember;
@end
使用
// 新建测试模型字典
NSDictionary *resDic = @{
@"Name":@"LOLITA0164",
@"sex":@(1),
@"currntAge":@(24),
@"height":@"170.0",
// @"money":@"0.112", // 可以没有对应字段
@"otherInfo":@{@"profession":@"iOS mobile development"},
@"isHandsome":@(YES),
@"familyMember":@[@"father",@"mother",@"brother",@"older sister"],
@"additional":@"我是测试条件" // 可以多余字段
};
// 映射字典
NSDictionary *hintDic = @{
@"name":@"Name",
@"age":@"currntAge"
};
Person *p = [Person objectWithModelDic:resDic hintDic:hintDic];
NSLog(@"\n姓名:%@\n性别:%ld\n年龄:%ld\n身高:%.1f\n存款:%.1f元\n其他信息%@\n帅不:%i\n家庭成员:%@",p.name,(long)p.sex,(long)p.age,p.height,p.money,p.otherInfo,p.isHandsome,p.familyMember);
运行结果:
结论:
1、只针对简单数据模型(无模型嵌套),完成字典转模型操作
2、需要更多的测试,后期需要完善
3、成员变量或属性皆可
简单数据模型转字典
.h
/**`这里写代码片`
模型转字典
@param hintDic 映射字典,如果不需要则nil
@return 结果字典
*/
-(NSDictionary*)changeToDictionaryWithHintDic:(NSDictionary*)hintDic;
.m
-(NSDictionary *)changeToDictionaryWithHintDic:(NSDictionary *)hintDic{
NSMutableDictionary *resDic = [NSMutableDictionary dictionary];
unsigned int numIvars; // 成员变量个数
Ivar *vars = class_copyIvarList([self class], &numIvars);
NSString *key=nil;
for(int i = 0; i < numIvars; i++) {
Ivar thisIvar = vars[i];
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)]; // 获取成员变量的名字
key = [key hasPrefix:@"_"]?[key substringFromIndex:1]:key; // 如果是属性自动产生的成员变量,去掉头部下划线
id value = [self valueForKey:key];
if (value!=nil) {
// 映射字典,转换key
if (hintDic) {
key = [hintDic objectForKey:key]?[hintDic objectForKey:key]:key;
}
[resDic setValue:value forKey:key];
}
}
free(vars);
return resDic;
}
使用
Person类
@interface Person : NSObject
{
@public
float height;
}
@property (strong ,nonatomic) NSString *name;
@property (assign ,nonatomic) NSInteger age;
@end
NSDictionary *dic = @{
@"name":@"LOLITA0164",
@"age":@(25),
@"height":@(170.0)
};
Person *p = [Person objectWithModelDic:dic hintDic:nil];
// key的映射(结果字典中的key)
NSDictionary *hintDic = @{
@"name":@"realName",
@"age":@"currentAge"
};
NSDictionary *resDic = [p changeToDictionaryWithHintDic:hintDic];
NSLog(@"%@",resDic);
结果:
补充
简单模型转json
.h
/**
模型转josn
@param hintDic 映射字典
@return 结果json
*/
-(NSString*)changeToJsonStringWithHintDic:(NSDictionary*)hintDic;
.m
-(NSString *)changeToJsonStringWithHintDic:(NSDictionary *)hintDic{
NSDictionary *resDic = [self changeToDictionaryWithHintDic:hintDic];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:resDic options:NSJSONWritingPrettyPrinted error:nil];
if (jsonData) {
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
return @"";
}
使用:
NSDictionary *dic = @{
@"name":@"LOLITA0164",
@"age":@(25),
@"height":@(170.0)
};
Person *p = [Person objectWithModelDic:dic hintDic:nil];
// key的映射(结果字典中的key)
NSDictionary *hintDic = @{
@"name":@"realName",
@"age":@"currentAge"
};
NSString *resString = [p changeToJsonStringWithHintDic:hintDic];
NSLog(@"--%@",resString);
结果:
2018-8-20 更新
在后续的使用过程中,发现了一些存在的问题,为此进行了一些优化,具体为:
1、类型不统一:如果服务端的某些字段和数据模型不统一(如数据模型为NSString类型,而服务器给出的类型为Number类型),这时就会发生错误
2、无法满足为某一个已经存在的数据模型追加一些数据的需求
3、无法满足模型转模型的需求
针对1的问题,我的同事进行了一些优化,将数据模型为NSString的类型一律通过创建字符串的形式进行转换,具体为:
id value = [propertyDic objectForKeyNotNull:key];
//(避免定义字符串,接收的却是Number的优化,待测试)
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(thisIvar)];//获取变量类型
if(value){
if([type containsString:@"NSString"]){
[self setValue:[NSString stringWithFormat:@"%@", value] forKey:key_property];
}else
[self setValue:value forKey:key_property];
}
注:其中的objectForKeyNotNull:
方法 是去除掉服务器给的NULL类型,具体为:字典NSDictionary的分类方法:
-(id)objectForKeyNotNull:(id)key{
id object = [self objectForKey:key];
if (object == [NSNull null]) {
return nil;
}
return object;
}
对于2的问题,我定义了一个实例方法,将为调用该实例方法的数据模型追加一些数据,新的实例方法为:
// !!!!: 追加一些属性(覆盖)
/**
追加一些属性(覆盖)
@param propertyDic 属性字典
@param hintDic 映射字典
*/
-(void)appendPropertyFromDic:(NSDictionary*)propertyDic hintDic:(NSDictionary*)hintDic;
针对3的问题,即模型转模型,这种情况通常会发生在多人合作的项目中,当两个分别有不同的开发者实现的模块需要交互时,有时会发生数据模型传递的问题,如果数据模型中设计的数据比较多,手动设置起来变的非常的困难,工作量极大,此时如果存在一种便捷的转换方法就再好不过了。
其实,模型转模型的方法也非常简单,只需要再将模型中的数据转换为字典,再将该字典作为数据源传给上面追加数据的方法即可,你也可以稍稍的将该字典做变操作,可以增加或者删除后再进行追加数据的操作。
为了方便使用者,我又自行添加了该方法,实现快捷的模型转模型
/**
从模型中追加一些属性(覆盖)
@param model 来源模型
@param hintDic 映射字典
*/
-(void)appendPropertyFromModel:(NSObject*)model hintDic:(NSDictionary*)hintDic;
另外,为了方便查看模型中的数据,定义了一个descriptionInfo
输出方法,将模型中的数据以字典的形式输出
测试部分
首先我们定义了一个Person
类,里面有各种类型
#import
typedef enum : NSInteger {
Male,
Famale,
} Sex;
@interface Person : NSObject
{
NSString* name;
}
@property (assign ,nonatomic) NSInteger age;
@property (assign ,nonatomic) Sex sex;
@property (assign ,nonatomic) CGFloat height;
@property (assign ,nonatomic) float width;
@property (strong ,nonatomic) NSString *address;
@end
我们创建一个实例,为此添加一些数据
// 模型的数据
NSDictionary* dic = @{
@"NAME":@"LOLITA0164",
@"age":@"26",
@"address":@123
};
Person* p = [Person objectWithModuleDic:dic hintDic:@{@"name":@"NAME"}];
// 输出模型数据
[p descriptionInfo];
结果:
上图可以看出,age
和address
字段分别可以接收了不同类型的数据
接下来我们为p数据模型追加一些数据
// 需要追加一些属性
NSDictionary* dic_app = @{
@"Sex":@(Famale),
@"height":@170.0,
@"width":@"120"
};
[p appendPropertyFromDic:dic_app hintDic:@{@"sex":@"Sex"}];
[p descriptionInfo];
结果:
结果表明,我们正确追加了一些数据
接下来我们测试一下数据模型转换数据模型,为此,我们定义一个People
类
#import
@interface People : NSObject
{
NSString* NAME;
}
@property (assign ,nonatomic) NSString* age;
@property (assign ,nonatomic) CGFloat count;
@end
使用People数据模型
// 定义一个新的数据模型
People* peo = [People new];
NSDictionary* dic_peo =@{
@"NAME":@"GUOGUO",
@"age":@"19",
@"count":@"12580"
};
// 需要追加一些属性
[peo appendPropertyFromDic:dic_peo hintDic:nil];
[peo descriptionInfo];
输出:
接下来,我们将使用peo
中的数据来覆盖p
中的数据
// 将peo模型的数据转换给p模型
[p appendPropertyFromModel:peo hintDic:@{@"name":@"NAME"}];
[p descriptionInfo];
结果
对比之前的数据
我们可以发现,peo
中的 name 和 age 数据成功转换给了p
总览
一些方法声明
#import
@interface NSObject (InitData)
/**
模型初始化
@param moduleDic 模型字典
@param hintDic 映射字典
@return 结果
*/
+(instancetype)objectWithModuleDic:(NSDictionary*)moduleDic hintDic:(NSDictionary*)hintDic;
/**
追加一些属性(覆盖)
@param propertyDic 属性字典
@param hintDic 映射字典
*/
-(void)appendPropertyFromDic:(NSDictionary*)propertyDic hintDic:(NSDictionary*)hintDic;
/**
从模型中追加一些属性(覆盖)
@param model 来源模型
@param hintDic 映射字典
*/
-(void)appendPropertyFromModel:(NSObject*)model hintDic:(NSDictionary*)hintDic;
/**
模型转字典
@param hintDic 映射字典,如果不需要则nil
@return 结果字典
*/
-(NSDictionary*)changeToDictionaryWithHintDic:(NSDictionary*)hintDic;
/**
模型转json
@param hintDic 映射字典
@return 结果json
*/
-(NSString*)changeToJsonStringWithHintDic:(NSDictionary*)hintDic;
/**
输出当前模型里的信息
@return 结果字典
*/
-(NSDictionary*)descriptionInfo;
@end
方法的实现部分
@implementation NSObject (InitData)
// !!!!: 模型初始化
+(instancetype)objectWithModuleDic:(NSDictionary *)moduleDic hintDic:(NSDictionary *)hintDic{
NSObject *instance = [[[self class] alloc] init];
[instance appendPropertyFromDic:moduleDic hintDic:hintDic];
return instance;
}
// !!!!: 追加一些属性(覆盖)
-(void)appendPropertyFromDic:(NSDictionary *)propertyDic hintDic:(NSDictionary *)hintDic{
unsigned int numIvars; // 成员变量
Ivar *vars = class_copyIvarList(self.class, &numIvars);
NSString* key = nil;
NSString *key_property = nil; // 属性
for (int i=0; i