#前言
###好久没有写过新文章了,最近一直在忙工作的事情,我的新浪微博开源项目也停止了一周时间,目前完成了60%,就先写一篇关于JSON转Model的文章和大家聊聊天吧,为什么会写一个这个小工具呢,请看文末😄
#核心方法Runtime的介绍
##1. Runtime是什么?
顾名思义:Runtime就是运行时的意思,是系统在运行时的一些机制,其中最主要的就是消息机制,举个常用的例子,在面向对象编程的语言中,万物皆对象,对象如何调用方法呢,
[target excuteSEL]
,需要一个对象,需要一个方法名,系统在运行时会自动转换成以下的形式:
objc_msgSend(target,@selector(excuteSEL:))
关于Runtime的详细介绍,网上有很多,这里就不做过多描述了。
##2.Runtime的常见用法
注:使用时需要#import <objc/objc-runtime.h>
* 1 方法替换(黑魔法)
举个例子来说明一下:
将调用A方法替换为调用B方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class_replaceMethod([self class], @selector(sendAMessage:), (IMP)changeAtoB, NULL); //MARK: 方法替换_1 - (void)sendAMessage:(NSString *)message { NSLog(@"A_message: %@",message); } - (void)sendBMessage:(NSString *)message { NSLog(@"B_Message: %@",message); } ViewController * changeAtoB(ViewController *SELF, SEL _cmd, NSString *message) { if ([NSStringFromSelector(_cmd) isEqualToString:@"sendAMessage:"]) { //将方法进行替换 [SELF sendBMessage:message]; } return SELF; } //这里IMP可以理解为魔法通道,将源方法通过IMP指针转换为目标方法
|
* 2 获取对象的属性和方法
注:获取对象的属性,这个方法在JSON转Model可以说是核心方法了
举例说明:
1 2 3 4 5 6 7 8 9
| //class_copyPropertyList 这个方法会获取到一个类.h和.m文件中interface中的所有属性 //获取CMGCD类的属性名称 unsigned int count; objc_property_t *propertyList = class_copyPropertyList([CMGCD class], &count); for (unsigned int i = 0; i < count; i++) { const char *propertyName = property_getName(propertyList[i]); NSLog(@"property ---> %@",[NSString stringWithUTF8String:propertyName]); }
|
1 2 3 4 5 6 7
| // unsigned int count; Method *methodList = class_copyMethodList([CMGCD class], &count); for (unsigned int i; i < count; i++) { Method method = methodList[i]; NSLog(@"Method ---> %@",NSStringFromSelector(method_getName(method))); }
|
* 2 设置对象关联
定义:关联是指把两个对象相互关联起来,使其中的一个对象作为另外一个对象的一部分
再举个例子,我在对象中定义了一个属性
1
| @property (strong, nonatomic) NSString *content;
|
1 2 3 4 5
| //设置对象属性关联 static char associatedKey; objc_setAssociatedObject(self, &associatedKey, @"content_yeah", OBJC_ASSOCIATION_RETAIN_NONATOMIC); NSString *get_content = objc_getAssociatedObject(self, &associatedKey); NSLog(@"content = %@",get_content);
|
Tips:设置对象关联需要以下几个要点:
源对象
、关键字
、关联的对象
关联策略
解释一下:这里我将@"content_yeah"
这个对象与self
使用OBJC_ASSOCIATION_RETAIN_NONATOMIC
策略关联到一起,意思就是在self
的生命周期之内关联的对象都不会被释放,通过这个方法,可以实现动态向类里面添加属性
另外还有一些关联的方法,如
断开关联: 设置关联对象为nil
即可
objc_setAssociatedObject(self, &associatedKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
断开这个对象的所有关联关系
1 2
| //断开所有关联 objc_removeAssociatedObjects(self);
|
#JSON转Model工具的主要介绍
1. 为什么会写这样一个工具
很多时候我们并不是缺少实力,而且是缺少一种彼可取而代之
的勇气,为什么会有MJExtension
、YYModel
的产生,查看源代码的过程中我有种想死的感觉,但是知道实现的原理后,为什么不能自己去实现一个呢?这个工具的源代码非常简单,我写这个工具的目的只是为了告诉朋友们,真的不复杂,不要因为看着复杂就放弃了自己动手的冲动
2. 工具的整体步骤简介
着重介绍一下我的思考过程
- 1 核心方法?
利用Runtime可以遍历出对象的所有属性,然后利用递归的思想逐层解析JSON
- 2 怎么去做?
基本所有的Model继承NSObject
,我们可以写一个NSObject
的Category
,然后在其中写一些解析方法,我们需要一个对照JSON字符串的解析路径字典,比如说JSON
的狗
的属性名称为dog
,我们的对象属性名称想定义为xiaogou
,这就需要手写一个字典将解析中遇到的dog
都给映射为xiaogou
- 3 开始动手吧
3. 主要代码介绍:
* 1 NSObject+CMModel
介绍
1 2 3 4 5 6 7 8
| //NSObject+CMModel.h // 单个对象 - (NSDictionary *)dict_CMModelWithClass; // 对象数组 - (NSDictionary *)dict_CMModelWIthArrayClass; - (instancetype)cm_initWithJSONString:(NSString *)jsonString;
|
下面着重解释一下.m文件中的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| //NSObject+CMModel.m #import <objc/runtime.h> #import "CMObject.h" #import "CMProperty.h" //返回单个对象的解析字典,默认为nil - ( NSDictionary * _Nullable )dict_CMModelWithClass { return nil; } //返回对象数组的解析字典,默认为nil - (NSDictionary *)dict_CMModelWIthArrayClass { return nil; } //调用方法 - (instancetype)cm_initWithJSONString:(NSString *)jsonString { if (self) { [self analysisWitnJsonString:jsonString]; } return self; }
|
取得对象的所有属性及其对应的类型
Tips: 这里自己写了一个类,将对象的属性及其名称封装到一个类型为CMProperty
的数组
1 2 3 4 5 6 7 8
| //属性名称 @property (strong, nonatomic) NSString *propertyName; //属性的类 @property (strong, nonatomic) Class propertyClass; //是否基本类型 @property (assign, nonatomic) BOOL isBasicType;
|
- 2 获取对象属性及其类型,并且将其封装为类型为
CMProperty
的数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| //将该类的属性和对应的类型进行封装 - (NSArray <CMProperty *> *)propertyArray { NSMutableArray *propertyArray = [NSMutableArray array]; unsigned int count; objc_property_t *propertyList = class_copyPropertyList([self class], &count); for (unsigned int i = 0; i < count; i++) { CMProperty *property = [[CMProperty alloc] init]; property.propertyName = [NSString stringWithUTF8String:property_getName(propertyList[i])]; NSString *attrs = @(property_getAttributes(propertyList[i])); NSUInteger dotLoc = [attrs rangeOfString:@","].location; NSString *propertyType = nil; NSUInteger loc = 1; if (dotLoc == NSNotFound) { // 没有找到 propertyType = [attrs substringFromIndex:loc]; }else { propertyType = [attrs substringWithRange:NSMakeRange(loc, dotLoc - loc)]; if ([propertyType isEqualToString:@"Q"]) { //基本类型 property.isBasicType = true; }else { propertyType = [propertyType substringWithRange:NSMakeRange(2, propertyType.length - 3)]; } } property.propertyClass = NSClassFromString(propertyType); [propertyArray addObject:property]; } free(propertyList); return propertyArray; }
|
开始解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| //进行解析 - (void)analysisWitnJsonString:(NSString *)json { //存在解析字典 if ([self dict_CMModelWithClass] != nil || [self dict_CMModelWIthArrayClass] != nil) { CMObject *analysisTools = [[CMObject alloc] initWithGoalObject:self CMPropertyArray:[self propertyArray]]; if ([self dict_CMModelWIthArrayClass]) { analysisTools.analysisObjectArrayDict = [self dict_CMModelWIthArrayClass]; } if ([self dict_CMModelWithClass]) { analysisTools.analysisDict = [self dict_CMModelWithClass]; } analysisTools.jsonString = json; } }
|
* 2 CMObject
介绍
这个类为实际进行解析工具的类,或者可以称之为工具,这里我们需要特殊对待NSArray
、NSDictionary
、int、float等基本类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| //CMObject.h //要解析的对象 @property (strong, nonatomic) id object; //解析key对应列表 单个对象 @property (strong, nonatomic) NSDictionary *analysisDict; //解析key对应列表 对象数组 @property (strong, nonatomic) NSDictionary *analysisObjectArrayDict; //属性列表 @property (strong, nonatomic) NSArray <CMProperty *> *propertyArray; //init方法 - (instancetype)initWithGoalObject:(id)object CMPropertyArray:(NSArray <CMProperty *> *)propertyArray; //解析的源JSON @property (strong, nonatomic) NSString *jsonString; BOOL Exist_(id obj,NSArray *existArray);
|
Tips: 下面代码可能看着会不舒服,我说一下整体的思路
* 1 NSObject+CMModel
中将封装的属性数组传递过来,我们一个接一个的对属性进行遍历构造
2 遍历详解:举例,碰到NSArray
的属性时,我们去看要解析的类中实现的`- (NSDictionary )dict_CMModelWIthArrayClass这个方法,找到目的对象的类型
OBJClass,然后将
JSON字典拆分后利用
- (instancetype)cm_initWithJSONString:(NSString *)jsonString这个方法创建一个
OBJClass类型的对象,并且添加到数组中,创建完后,使用
KVC`将数组赋给源对象,具体代码看下面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| //CMObject.m //这里说一下解析方法, 代码较多 //开始进行model解析 - (void)analysisModel { NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:[_jsonString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil]; //将`NSObject+CMModel`中获取的属性数组进行遍历构造,并赋值给对应的属性 [_propertyArray enumerateObjectsUsingBlock:^(CMProperty * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { //单独处理数组类型 if (obj.propertyClass == [NSArray class]) { // 对象为数组类型, NSString *key = [_analysisObjectArrayDict objectForKey:obj.propertyName]; Class OBJClass = NSClassFromString(key); if (![jsonDict objectForKey:key]) { //名称与json中的名称并不相符 if ([_analysisDict objectForKey:obj.propertyName]) { NSMutableArray *objectArray = [NSMutableArray array]; NSArray *obj_json_array = [jsonDict objectForKey:[_analysisDict objectForKey:obj.propertyName]]; [obj_json_array enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj_dict, NSUInteger idx, BOOL * _Nonnull stop) { id objx = [[OBJClass alloc] cm_initWithJSONString:DataToJSONString(obj_dict)]; //解析好对象之后,存到数据中 [objectArray addObject:objx]; }]; [_object setValue:objectArray forKey:obj.propertyName]; }else { } } }else if (Exist_(obj.propertyClass, @[[NSDictionary class],[NSMutableDictionary class]])) { NSDictionary *obj_dict = [jsonDict valueForKey:[_analysisDict objectForKey:obj.propertyName]]; if (!obj_dict) { [_object setValue:[NSNull null] forKey:obj.propertyName]; }else { [_object setValue:obj_dict forKey:obj.propertyName]; } }else if (obj.isBasicType){ //基本类型 id num = [jsonDict valueForKey:[_analysisDict objectForKey:obj.propertyName]]; if (!num || num == [NSNull null]) { //为空判断 //这里我们默认设置为 0.00大小的NSNumber对象 [_object setValue:NULL_NUM forKey:obj.propertyName]; }else { NSNumber *number = (NSNumber *)num; [_object setValue:number forKey:obj.propertyName]; } }else { Class OBJClass = obj.propertyClass; if(!Exist_(OBJClass, foundationClasses_)) { //如果类型为自定义类 id obj_dict = [jsonDict valueForKey:[_analysisDict objectForKey:obj.propertyName]]; //非空判断 if (!obj_dict) { id objx = [[OBJClass alloc] cm_initWithJSONString:DataToJSONString(obj_dict)]; [_object setValue:objx forKey:obj.propertyName]; }else { //这里可以赋值为[NSNull null] 可以赋值为一个新对象 // [_object setValue:[NSNull null] forKey:obj.propertyName]; [_object setValue:[[OBJClass alloc] init] forKey:obj.propertyName]; } }else { [_object setValue:[jsonDict valueForKey:[_analysisDict objectForKey:obj.propertyName]] forKey:obj.propertyName]; } } }]; }
|
4. 如何使用
举例说明:
这里有三个类
Animals(动物)
1 2
| @property (strong, nonatomic) NSArray <Dog *> *dogs; @property (strong, nonatomic) NSArray <Pig *> *pigs;
|
—实现方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| - (NSDictionary *)dict_CMModelWithClass { return @{ @"dogs" : @"dog", @"pigs" : @"pig", }; } - (NSDictionary *)dict_CMModelWIthArrayClass { return @{ @"dogs" : @"Dog", @"pigs" : @"Pig", }; }
|
Dog(狗)
1 2 3 4 5
| @property (strong, nonatomic) NSString *dog_name; @property (assign, nonatomic) NSUInteger dog_age; //狗养的猪 @property (strong, nonatomic) Pig *dog_pig;
|
—实现方法
1 2 3 4 5 6 7
| - (NSDictionary *)dict_CMModelWithClass { return @{ @"dog_age" : @"age", @"dog_name" : @"name", @"dog_pig" : @"dog_pig" }; }
|
Pig(猪)
1 2
| @property (strong, nonatomic) NSString *pig_name; @property (assign, nonatomic) NSUInteger pig_age;
|
—实现方法
1 2 3 4 5 6
| - (NSDictionary *)dict_CMModelWithClass { return @{ @"pig_age" : @"age", @"pig_name" : @"name" }; }
|
调用方法
1 2 3
| NSString *testJSON = @"{\"dog\":[{\"name\":\"dog_1\",\"age\":15,\"dog_pig\":{\"name\":\"dogAndPig1\",\"age\":666}},{\"name\":\"dog_2\",\"age\":null,\"dog_pig\":null}],\"pig\":[{\"name\":\"pig_1\",\"age\":10},{\"name\":\"pig_2\",\"age\":12}]}"; Animals *animals = [[Animals alloc] cm_initWithJSONString:testJSON];
|
#写在后面的话
这个项目并不完善,比如说对于其中日期的格式化,非空的一些判断等,其中也有一些bug,本文权当是抛砖引玉,利用Runtime
可以做很多事情,比如你可以实现,一句话完成归档与解归档,不会再出现Model属性过多时重写initWithCoder
和encodeWithCoder
的尴尬了,so,有时候我们更缺的是一种思考问题的方式,共勉!
PS:欢迎来我的简书、Github、个人博客交流😄
文中的Demo下载地址