字典转模型在iOS开发中属于非常常见的操作.
比较简单的方式是 KVC ,但是这样会有一个局限 ,就是 模型中的属性必须和字典中的完全匹配,否则,KVC将会报错.
手动编写代码虽然比较灵活,但是如果字典中数据量庞大,也会是一个糟糕的体验.
针对以上问题,利用runtime实现一个更加灵活的字典转模型.
1.为 NSObject建立一个分类
NSObject+GQRuntimeTool.h文件
#import <Foundation/Foundation.h>
@interface NSObject (GQRuntimeTool)
-(void)objectWithDictionary:(NSDictionary*)dict;
@end
NSObject+GQRuntimeTool.m文件
#import "NSObject+GQRuntimeTool.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation NSObject (GQRuntimeTool)
-(void)objectWithDictionary:(NSDictionary*)dict
{
Class cls = [self class];
unsigned int count;
//Ivar instance variable 就是类的成员属性
//通过运行时函数 获得 类内部的成员属性个数 放到 count 的地址
//返回说是一个指针 ,所指向的内存空间为 首个 Ivar 的内存地址,这个相当于数组名
Ivar *ivarArr = class_copyIvarList(cls, &count);
//遍历数组( 数组名存放的是数组首地址,就是一个指针)
for(int i =0;i<count;i++)
{
//把 Ivar 当成一个基本类型 比如 double 或者 int
Ivar ivar = ivarArr[i];
//获取属性名
const char* name = ivar_getName(ivar);
//获取属性类型
NSString* typeName =[NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
NSLog(@"%@",typeName); //发现 int 的类型是 i
//1.将 c 字符串转换成 OC 的
NSMutableString * ocVaribaleName = [NSMutableString stringWithUTF8String:name];
//2.删除前面的 _
[ocVaribaleName deleteCharactersInRange:NSMakeRange(0, 1)]; // id_str
//3.从字典取值的时候,使用的 key
NSString *key = [ocVaribaleName copy];
//4.取值
id value = dict[key];
//5.将 id_str 转换成 setId_Str:
NSString * setter = [self setterNameWithCString:name];
//6.使用运行时填充数据
if ([typeName isEqualToString:@"i"]) {
int intValue =[value intValue];
objc_msgSend(self, NSSelectorFromString(setter),intValue);
}else if([typeName isEqualToString:@"@\"NSString\""])
{
NSString *str = [NSString stringWithFormat:@"%@",value];
objc_msgSend(self, NSSelectorFromString(setter),str);
}//如果有其他类型,需要自己适配一下...
/**
如果有其他类型,需要自己适配一下
int i
double d
bool B
*/
}
}
/**
* 利用c字符串获得 setter方法的名字
*
*/
-(NSString*)setterNameWithCString:(const char*) cstr
{
char * strPointer = NULL;
//此处不用 oc自带capitalizedString 的原因是发现他会将 id_str 大写成 Id_Str 而不是 Id_str
CapicializeString(cstr, &strPointer);
NSString * setter = [NSString stringWithUTF8String:strPointer];
free(strPointer);
return setter;
}
void CapicializeString( const char * string,char ** s){
(*s) = malloc(strlen(string)+6);
//拼出格式,并去掉 _ 现在的格式是 setid_str
sprintf((*s),"set%s:",(string+1));
//大写 set后面的第一个字母
if(('a'<=(*s)[3])&&((*s)[3]<='z')) (*s)[3]-=32;
}
@end
2. 模型对象
GQModel.h
#import <Foundation/Foundation.h>
#import "GQToy.h"
@interface GQModel : NSObject
/**
* 唯一标识符
*/
@property (nonatomic,copy)NSString *id_str;
/**
* 姓名
*/
@property (nonatomic,copy)NSString *name;
/**
* 年龄
*/
@property (nonatomic,assign)int age;
/**
* 性别
*/
@property (nonatomic,copy)NSString *sex_str;
@end
.m文件无内容
3.在controller中
- (void)viewDidLoad
{
[super viewDidLoad];
//这个字典在实际工作中,都是来自 JSon对象的,此处仅仅供测试
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
@"22",@"id_str",
@33,@"age",
@"Grace",@"name",
@"male",@"sex_str",
nil];
GQModel * mo = [[GQModel alloc]init];
[mo objectWithDictionary:dict];
if (mo) {
NSLog(@"mo的id是:%@\n",mo.id_str);
NSLog(@"mo的名字是:%@\n",mo.name);
NSLog(@"mo的年龄是:%d\n",mo.age);
NSLog(@"mo的性别是:%@\n",mo.sex_str);
}
}
运行打印结果如下:
如果以后字典数据发生变化,只需要修改模型中的属性名即可.
是不是非常简单呢?