iOS 字数据模型相互转换

思路

通过运行时获取成员变量,通过 KVC 的形式对其赋值。

  1. 在 NSObject 分类中新增初始化方法,将字典转换为模型对象的属性
  2. 使用运行时来得到对象的成员变量,使用KVC方式将字典中各个字段赋值给对应的属性
  3. 针对字典的key不同于成员变量的问题,可以传递一个映射字典来解决

实现

步骤一:

在NSObject中新增初始化方法

@interface NSObject (InitData)
/**
 模型初始化
 @param ModelDic 模型字典
 @param hintDic 映射字典,如果不需要则nil
 @return 模型对象
 */
+(instancetype)objectWithModelDic:(NSDictionary*)modelDic hintDic:(NSDictionary*)hintDic;
@end


#import <objc/runtime.h>
#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

这样,所有数据模型都可以直接调用这个方法完成字典转模型操作了,而不用在单独为每个模型编写初始化方法

简单使用

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、成员变量或属性皆可

简单数据模型转字典

/** 
 模型转字典
 @param hintDic 映射字典,如果不需要则nil
 @return 结果字典
 */
-(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;
}

简单模型转json

-(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 @"";
}

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类,里面有各种类型

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];

结果:

结果

上图可以看出,ageaddress字段分别可以接收了不同类型的数据

接下来我们为p数据模型追加一些数据

// 需要追加一些属性
NSDictionary* dic_app = @{
                       @"Sex":@(Famale),
                       @"height":@170.0,
                       @"width":@"120"
                       };
[p appendPropertyFromDic:dic_app hintDic:@{@"sex":@"Sex"}];
[p descriptionInfo];

结果:


结果

结果表明,我们正确追加了一些数据

接下来我们测试一下数据模型转换数据模型,为此,我们定义一个People

@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

总览

一些方法声明

@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<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 = [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];
        }
    }
    free(vars);
}

// !!!!: 从模型中追加一些属性(覆盖)
-(void)appendPropertyFromModel:(NSObject *)model hintDic:(NSDictionary *)hintDic{
    NSDictionary* propertyDic = [model changeToDictionaryWithHintDic:nil];
    [self appendPropertyFromDic:propertyDic hintDic:hintDic];
}

// !!!!: 模型转字典
-(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;
}

// !!!!: 模型转josn
-(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 *)descriptionInfo{
    NSDictionary *dic = [self changeToDictionaryWithHintDic:nil];
    NSLog(@"%@",dic);
    return dic;
}
@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容