iOS中字典转模型的方法及底层原理

1 自动打印属性字符串分类
  • 提供一个分类,专门根据字典生成对应的属性字符串。
@implementation NSObject (Property)

+ (void)PH_createPropertyCodeWithDict:(NSDictionary *)dict
{
    NSMutableString *strM = [NSMutableString string];
    // 遍历字典
    [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull propertyName, id  _Nonnull value, BOOL * _Nonnull stop) {
        NSLog(@"%@,%@",propertyName,[value class]);

        NSString *code;
        if ([value isKindOfClass:NSClassFromString(@"__NSCFString")]) {
            code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;",propertyName]
            ;
        } else if ([value isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
            code = [NSString stringWithFormat:@"@property (nonatomic, assign) int %@;",propertyName]
            ;
        }else if ([value isKindOfClass:NSClassFromString(@"__NSCFArray")]){
            code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",propertyName]
            ;
        }else if ([value isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
            code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",propertyName]
            ;
        } else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
            code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",propertyName]
            ;
        }
        [strM appendFormat:@"\n%@\n",code];
    }];

    NSLog(@"%@",strM);
}
@end
2 字典转模型的方式一:KVC
@implementation Status


+ (instancetype)statusWithDict:(NSDictionary *)dict
{
    Status *status = [[self alloc] init];

    [status setValuesForKeysWithDictionary:dict];

    return status;

}

@end
  • KVC字典转模型弊端:必须保证,模型中的属性和字典中的key一一对应。
    • 如果不一致,就会调用[<Status 0x7fa74b545d60> setValue:forUndefinedKey:]
      key找不到的错。
    • 分析:模型中的属性和字典的key不一一对应,系统就会调用setValue:forUndefinedKey:报错。
    • 解决:重写对象的setValue:forUndefinedKey:,把系统的方法覆盖,
      就能继续使用KVC,字典转模型了。
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{

}
3 字典转模型的方式二:Runtime
  • KVC:遍历字典中所有key,去模型中查找有没有对应的属性名
  • runtime:遍历模型中所有属性名,去字典中查找

1.思路与步骤

<!--* 思路:利用运行时,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。-->
<!--* 步骤:提供一个NSObject分类,专门字典转模型,以后所有模型都可以通过这个分类转。-->

2.分类

+ (instancetype)PH_modelWithDict:(NSDictionary *)dict
{
    // 创建对应类的对象
    id objc = [[self alloc] init];

    // runtime:遍历模型中所有成员属性,去字典中查找
    // 属性定义在哪,定义在类,类里面有个属性列表(数组)

    // 遍历模型所有成员属性
    // ivar:成员属性
    // class_copyIvarList:把成员属性列表复制一份给你
    // Ivar *:指向Ivar指针
    // Ivar *:指向一个成员变量数组
    // class:获取哪个类的成员属性列表
    // count:成员属性总数

    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList(self, &count);
    for (int i = 0; i < count; i++) {
        // 获取成员属性
        Ivar ivar = ivarList[i];
        // 获取成员名
        NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        //成员属性类型
        NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        //获取key
        NSString *key = [propertyName substringFromIndex:1];
        //获取字典的value
        id value = dict[key];
        // 给模型的属性赋值
        // value:字典的值
        // key:属性名

// user:NSDictionary
        //** '二级转换'**
          // 值是字典,成员属性的类型不是字典,才需要转换成模型

        if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"]) {
            // 需要字典转换成模型
            // 转换成哪个类型

            // @"@\"User\"" User
            NSRange range = [propertyType rangeOfString:@"\""];
            propertyType = [propertyType substringFromIndex:range.location + range.length];
            // User\"";

            range = [propertyType rangeOfString:@"\""];
            propertyType = [propertyType substringFromIndex:range.location];
            // 字符串截取

            // 获取需要转换类的类对象
            Class modelClass = NSClassFromString(propertyType);
            if (modelClass) {
                value = [modelClass PH_modelWithDict:value];
            }

        }

<!-- 三级转换:NSArray中也是字典,把数组中的字典转换成模型.-->
        // 判断值是否是数组
        if ([value isKindOfClass:[NSArray class]]) {
            //判断对应类有没有实现字典数组转模型数组的协议
            if ([self respondsToSelector:@selector(PH_arrayContainModelClass)]) {

                // 转换成id类型,就能调用任何对象的方法
                id idSelf = self;

                // 获取数组中字典对应的模型
                NSString *type =  [idSelf PH_arrayContainModelClass][key];

                // 生成模型
                Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];

                // 遍历字典数组,生成模型数组
                for (NSDictionary *dict in value) {
                    // 字典转模型
                    id model = [classModel PH_modelWithDict:dict];
                    [arrM addObject:model];
                }
                // 把模型数组赋值给value
                value = arrM;
            }
        }


        if (value) {
            // kvc赋值:不能传空
            [objc setValue:value forKey:key];
        }
        NSLog(@"%@",key);
        NSLog(@"%@,%@",propertyName,propertyType);
    }

    return objc;
}

3.转换

- (void)viewDidLoad {
    [super viewDidLoad];

    // 解析
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
    NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
    NSArray *dictArr = dict[@"statuses"];

    // 设计模型属性代码

    NSMutableArray *statuses = [NSMutableArray array];

    for (NSDictionary *dict in dictArr) {
        // 字典转模型
        Status *status = [Status PH_modelWithDict:dict];
        [statuses addObject:status];
    }
    NSLog(@"%@",statuses);

}

KVC: Key Value Coding (键值编码)

在iOS开发中,KVC是我们经常要使用的技术.那么KVC有什么作用呢?简单列举一下下面几种:

  • 取值和赋值(开发中基本不用)
  • 获取对象私有变量的值.(经常使用,例如UIPageContorl分页, 设置圆点为图片)
  • 改变对象私有变量的值(经常使用)
  • 简单的字典转模型(偶尔使用)
  • 模型转字典
  • 批量取值

KVC字典转模型的底层实现

  • 通常我们手动将字典转模型的话,会在模型中提供一个类方法接收一个字典,在这个方法中将字典转换成模型,再将转换好的模型返回.

    + (instancetype)statusWithDict:(NSDictionary *)dict
    {
      Status *status = [[self alloc] init];
      //利用KVC字典转模型
      [status setValuesForKeysWithDictionary:dict];
    
      return status;
    }
  • 分析一下[status setValuesForKeysWithDictionary:dict]的底层实现原理

    + (instancetype)statusWithDict:(NSDictionary *)dict
    {
      Status *status = [[self alloc] init];
      //利用KVC字典转模型
      //[status setValuesForKeysWithDictionary:dict];
    
      //setValuesForKeysWithDictionary:原理--遍历字典中所有的key,去模型中查找对应的属性,把值给模型属性赋值
    
      [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
          // 这行代码才是真正给模型的属性赋值
          [status setValue:obj forKey:key];
      }];
      return status;
    }
  • KVC字典转模型弊端:必须保证,模型中的属性和字典中的key一一对应。如果不是一一对应的话,就会报错,仔细看一下错误信息,[<Status 0x7fd439d20a60> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key source.是系统调用了setValue:forUndefinedKey:报错.如果想解决这个问题,只需要在模型中重写对象的setValue:forUndefinedKey:,把系统的方法覆盖, 就能继续使用KVC,字典转模型了。
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key
    {
    }

啰嗦一点KVC的setValue:forKey:方法赋值的原理

  • 首先会去模型中查找有没有对应key的setter方法,有就直接调用set方法方法赋值.
  • 上一步没有的话,去模型中查找有没有和key同名的属性,有的话赋值给与key同名的属性.
  • 上一步还没有的话,去属性中查找有没有和key同名的带下划线的属性,有的话直接赋值.
  • 如果再没有,那就直接调用对象的 setValue:forUndefinedKey:直接报错

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值