继续进行优秀开源框架的源码学习,此次打算学习一些经常使用的model解析的框架,好比YYModel,MJExtension,Mantle等。我本身用过YYModel和MJExtension,比较简单易用,看过别人用Mantle的代码,我的感受稍微繁琐一些,因此此次就先学习MJExtension吧。json
本次的学习我分为了两个过程:api
本文主要是记录第一个过程当中的学习和心得。数组
MJExtension从最初到如今,也已经更新了几十个版本了。因此在开始以前,咱们先查阅一些别的资料,从大概上来了解一下MJExtension的实现原理和一些学习的点。框架
参考文章post
就拿最简单的json转model来讲,个人我的观点,其实主要是运行时机制和递归思想相结合来实现。经过运行时机制,咱们能够获取到一个类的全部属性,而后经过遍从来对每个属性进行赋值,若是该属性又是一个自定义的类,那就用到递归的思想这样一级级的解析下去,直到解析完成。数组也是同样的,只是多了一个对数组遍历的环节。学习
如今让咱们具体来看MJExtension初版本的代码(如下所提到MJExtension都是指的它的初版,特殊状况会单独指出)优化
MJExtension中最主要的就是NSObject+MJKeyValue
这个类,他经过分类的形式向咱们提供了dict->model的方法,而后其中重要的方法- (instancetype)setKeyValues:(NSDictionary *)keyValues
debug
下面是其中的代码:code
- (instancetype)setKeyValues:(NSDictionary *)keyValues { MJAssert2([keyValues isKindOfClass:[NSDictionary class]], self); [[self class] enumerateIvarsWithBlock:^(MJIvar *ivar, BOOL *stop) { // 1.取出属性值 id value = keyValues ; for (NSString *key in ivar.keys) { value = value[key]; } if (!value || value == [NSNull null]) return; // 2.若是是模型属性 MJType *type = ivar.type; Class typeClass = type.typeClass; if (!type.isFromFoundation && typeClass) { value = [typeClass objectWithKeyValues:value]; } else if (typeClass == [NSString class]) { if ([value isKindOfClass:[NSNumber class]]) { // NSNumber -> NSString value = [_numberFormatter stringFromNumber:value]; } else if ([value isKindOfClass:[NSURL class]]) { // NSURL -> NSString value = [value absoluteString]; } } else if ([value isKindOfClass:[NSString class]]) { if (typeClass == [NSNumber class]) { // NSString -> NSNumber value = [_numberFormatter numberFromString:value]; } else if (typeClass == [NSURL class]) { // NSString -> NSURL value = [NSURL URLWithString:value]; } } else if (ivar.objectClassInArray) { // 3.字典数组-->模型数组 value = [ivar.objectClassInArray objectArrayWithKeyValuesArray:value]; } // 4.赋值 [ivar setValue:value forObject:self]; }]; // 转换完毕 if ([self respondsToSelector:@selector(keyValuesDidFinishConvertingToObject)]) { [self keyValuesDidFinishConvertingToObject]; } return self; }
能够看的出这个方法里面的主要代码就是enumerateIvarsWithBlock
回调里面的代码,回调中返回了一个MJIvar
的类,MJIvar其实就是对你的model类里面的每个成员变量作的进一步的封装,封装后每个成员变量对应对封装成一个MJIvar的实例。component
MJIvar中的大多数字段都是起到了一个标识和记录的做用,好比说成员变量的名、属于哪一个类等,其中还包含一个MJType类型的属性,其实也是作一些标识的做用,你们点进去看看就一目了然了。
作这一层的封装主要是为了以后的处理值时使用。
上述代码的中间部分大篇的if,else if的判断就是在作值的分类处理
MJType *type = ivar.type; Class typeClass = type.typeClass; if (!type.isFromFoundation && typeClass) { value = [typeClass objectWithKeyValues:value]; }
这第一个判断,就是用于若是model中的某个成员变量仍是一个自定义类的状况,type中的isFromFoundation字段就是标识改为员变量的类是不是自定义的类,若是是自定义的类,把这个类存进type.typeClass下面。value = [typeClass objectWithKeyValues:value];
这句代码也就是递归思想的提现,若是这个成员变量是一个自定义类的,那么该成员变量对应的值应该也是一个model,因此用这个二级的model类继续调用objectWithKeyValues
方法,继续解析下去。
若是是数组,调用objectArrayWithKeyValuesArray
这个方法,原理相同,只是多一层的遍历。
全部的解析,最后都调用了
// 4.赋值 [ivar setValue:value forObject:self];
这个是MJIvar中的方法
- (void)setValue:(id)value forObject:(id)object { if (_type.KVCDisabled) return; [object setValue:value forKey:_propertyName]; }
这里_propertyName的也是MJIvar的一个字段,就是记录封装成MJIvar以前的这个成员变量的名字,而后使用setValue:forKey
为一个类的成员变量赋值。
上面的是全部的成员变量已经封装成MJIvar以后,遍历全部的MJIvar并最终赋值的过程,还有一个方法也很重要,他实现了获取全部的成员变量并封装的这个过程的。下面看+ (void)enumerateIvarsWithBlock:(MJIvarsBlock)block
这个方法
+ (void)enumerateIvarsWithBlock:(MJIvarsBlock)block { static const char MJCachedIvarsKey; // 得到成员变量 NSMutableArray *cachedIvars = objc_getAssociatedObject(self, &MJCachedIvarsKey); if (cachedIvars == nil) { cachedIvars = [NSMutableArray array]; [self enumerateClassesWithBlock:^(__unsafe_unretained Class c, BOOL *stop) { // 1.得到全部的成员变量 unsigned int outCount = 0; Ivar *ivars = class_copyIvarList(c, &outCount); // 2.遍历每个成员变量 for (unsigned int i = 0; i<outCount; i++) { MJIvar *ivar = [MJIvar cachedIvarWithIvar:ivars[i]]; ivar.key = [self ivarKey:ivar.propertyName]; // 若是有多级映射 ivar.keys = [ivar.key componentsSeparatedByString:@"."]; // 数组中的模型类 ivar.objectClassInArray = [self ivarObjectClassInArray:ivar.propertyName]; ivar.srcClass = c; [cachedIvars addObject:ivar]; } // 3.释放内存 free(ivars); }]; objc_setAssociatedObject(self, &MJCachedIvarsKey, cachedIvars, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } // 遍历成员变量 BOOL stop = NO; for (MJIvar *ivar in cachedIvars) { block(ivar, &stop); if (stop) break; } }
你们能够看到,它主要的部分,又是一个遍历以后的回调, 咱们那顺便贴出来这个遍历的代码
+ (void)enumerateClassesWithBlock:(MJClassesBlock)block { // 1.没有block就直接返回 if (block == nil) return; // 2.中止遍历的标记 BOOL stop = NO; // 3.当前正在遍历的类 Class c = self; // 4.开始遍历每个类 while (c && !stop) { // 4.1.执行操做 block(c, &stop); // 4.2.得到父类 c = class_getSuperclass(c); if ([MJFoundation isClassFromFoundation:c]) break; } }
很容易看出来,这是为了处理那种父类也是自定义类的状况,那咱们实际开发中来讲,通常后台返回的数据都是有固定形式的,好比说status,message,code这种字段是每一个接口都返回的,因此这些字段我通常都写在一个父类里面,而后这个循环就是遍历出父类,为从父类中继承的字段赋值。
说回上一个遍历方法,其实就是一个封装过程,从代码中能够看出来,使用了一些runtime
的api来获取了类的成员变量,而后经过循环对每一个变量进行了封装。这一步的话,光这样干很难体验什么,你们能够跑一个简单的例子,而后debug跟一下,看看MJIvar中每一个属性表明什么。
有些状况可能要映射字段名,好比id属于关键字,可能公司要求model中不容许用id做为成员变量名, 因此要作映射处理,还有若是数组中若是包含别的model,这个组数中的model类名咱们也应该告诉MJExtension。
MJExtension都抛出了方法,须要映射名称使用replacedKeyFromPropertyName
,数组包含模型使用objectClassInArray
,咱们根据本身的须要的重写相应方法。
举个例子:
成员变量属因而数组的状况下, 这个数组里面包含model类型要存在ivar.objectClassInArray下面
// 数组中的模型类 ivar.objectClassInArray = [self ivarObjectClassInArray:ivar.propertyName];
这是ivarObjectClassInArray
的实现
+ (Class)ivarObjectClassInArray:(NSString *)propertyName { if ([self respondsToSelector:@selector(objectClassInArray)]) { return self.objectClassInArray[propertyName]; } else { // 为了兼容之前的对象方法 id tempObject = self.tempObject; if ([tempObject respondsToSelector:@selector(objectClassInArray)]) { id dict = [tempObject objectClassInArray]; return dict[propertyName]; } return nil; } return nil; }
在赋值的时候会先判断是否respondsToSelector
,而后根据状况赋值。
初版的代码比较简单,我就简单的dict->model说了一下,model->dict你们能够本身再去看看,固然其余还有一些细节处理的东西, 你们也能够经过代码来进一步学习。
对MJExtension学习的第一步就先到这,慢慢我会继续看它的新的代码,学习他的一些优化和封装。