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

xiaoxiao2021-02-28  202

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:直接报错
转载请注明原文地址: https://www.6miu.com/read-62398.html

最新回复(0)