model和json转换时开发中普遍使用的,在iOS开发中系统KVO本身也提供了简单的API:
同时常用流行的model和json的转化框架有MJExtension、Mantle、JSONModel、YYModel等。今天主要就YYModel得源码做一个剖析,延续之前风格,还是通过一个典型的应用展开分析。
首先让我们看一下这个框架的基本结构:
可以看到这是一个很轻量简洁的工具,只有四个文件,其中NSObject+YYModel是NSObject的categary提供了相应的使用接口,和Mantle相比没有侵入性,YYClassInfo类是针对NSObject的Ivar、Property、Method进行封装的类,之后具体分析。
下边先看一个调用:
以上是YYKit的demo中一个简单实例。
之下就从json转model的API开始分析:
由NSDictionary *dic = [self _yy_dictionaryWithJSON:json]可以看出,由于传入的json是id类型,所以首先要将其转化为NSDictionary*类型,然后就变了字典转模型。先具体分析一下这个将json转化为字典的方法:
可看出如果json为空,直接返回的dic也是空,如果json是字典直接返回,如果是字符串则调用系统方法jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding]将其转化为NSData*类型的jsonData,如果json是NSData*类型则直接将json返给jsonData,然后调用系统方法dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL]将jsonData转化为字典dic并返回。
接下来看这个将字典转化为model的方法:[self modelWithDictionary:dic]:
如果传入参数dictionary为空或不是字典,返nil。
Class cls = [self class]返回类本身。
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls]创建了一个_YYModelMeta*类型的对象modelMeta。
接下来就从这个初始化工厂方法入手,详细分析_YYModelMeta这个类:
方法前半部分说明,cache是在程序运行期间只生成一次的静态变量,lock在这里是用信号量实现的安全锁,从_YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls))可以看出cache是在程序运行期间缓存每个cls对应的_YYModelMeta *类型的meta对象。
所以这个方法就是如果全局还没有cache,则创建cache,并创建安全锁lock,然后从cache中获取以cls为key值对应的_YYModelMeta*类型的对象,如果没有或对象需要更新则调用meta = [[_YYModelMeta alloc] initWithClass:cls]创建meta对象,并插入到cache中,整个过程通过lock锁保证了线程安全。
在深入探讨具体的初始化成员方法:
可以看出这是一个很庞大的方法。YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls]这一步是闯将一个YYClassInfo*类型的对象classInfo,开篇提到过YYClassInfo是用来存储一些类信息的,现在就具体深入到这个方法中:
类似于_YYModelMeta的工厂方法,先在缓存中查YYClassInfo *info,如果没有,则调用info = [[YYClassInfo alloc] initWithClass:cls]方法创建,创建完后将其存入相应的缓存中(classCache或metaCache)。
再深入探查YYClassInfo的具体初始化方法:
在这个方法中依次保存了@property (nonatomic, assign, readonly) Class cls,@property (nullable, nonatomic, assign, readonly) Class superCls,@property (nullable, nonatomic, assign, readonly) Class metaCls,@property (nonatomic, strong, readonly) NSString *name这些属性。
再看[self _update]这个方法做了什么:
可以看出这个方法主要是为@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos,@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos,@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos这三个属性赋值。
其中_ivarInfos存储了类的所有成员变量,_methodInfos类的所有成员方法,_propertyInfos存储了类的所有属性。
以_methodInfos为类:
先是通过runtime方法Method*methods =class_copyMethodList(cls, &methodCount)获取类的所有方法,然后遍历方法,针对每个方法创建一个YYClassMethodInfo *类型的info:
可以看出YYClassMethodInfo保存了方法名、方法选择子、函数指针、参数类型等,然后调用方法if(info.name) methodInfos[info.name] = info以方法名字为key值,以YYClassMethodInfo *info为value值,将所有方法存进_methodInfos字典中。_ivarInfos和_propertyInfos类似,也都是用字典存储了所有的属性和成员变量。最后设置_needUpdate = NO。
再回到YYClassInfo的- (instancetype)initWithClass:(Class)cls方法中:_superClassInfo = [self.class classInfoWithClass:_superCls]再依次创建模型父类的YYClassInfo*类型的对象并保存在缓存中。
接着回到_YYModelMeta的- (instancetype)initWithClass:(Class)cls方法中。
统计黑名单和白名单。
这一步是获取容器属性中的类型信息。
这一步遍历之前生成的propertyInfos,先根据黑名单和白名单做相应的过滤,针对需要在模型转化中应用的属性进行一个_YYModelPropertyMeta*类型的封装,然后保存在allPropertyMetas中,并将allPropertyMetas字典中的所有value值赋给_allPropertyMetas属性。
如果模型自定义的属性和字典的key值不一致的话,这一步是建立一个影射(需要在模型中实现modelCustomPropertyMapper方法,在这个方法中返回映射字典)。然后将相关影射信息存在_mapper属性中。
最后对_YYModelMeta*类型的meta进行赋值,然后返回。
就此,_YYModelMeta类的- (instancetype)initWithClass:(Class)cls方法结束。
返回到+ (instancetype)metaWithClass:(Class)cls方法中:
将_YYModelMeta*meta放入缓存中,这个方法也结束。
经过如此繁杂的操作,你是否已经迷路了呢,现在回到字典转模型这一步了:
上面的操作就是_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls]这一步,这一步就是将模型类的相关属性、成员变量、方法、黑名单、白名单、模型属性和字典索引之间影射等信息取出来并全局缓存,在下次调用的时候直接获取可以大大提高运行速度。
下面就开始分析将字典转化为模型的方法:
先创建一个模型对象,再通过调用方法[one modelSetWithDictionary:dictionary]给对象赋值:
在这个方法中调用CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas, CFRangeMake(0, modelMeta->_keyMappedCount), ModelSetWithPropertyMetaArrayFunction, &context)方法赋值。
CFArrayApplyFunction对第一个数组类型的参数在第二个CFRange类型参数区间中遍历,没遍历一次调用一次第三个函数指针类型的参数指向的函数,第四个参数为调用的函数的第二个参数,每遍历一次的数组元素为函数的第一个参数。
可以看出第三个参数指向的函数式具体的对每一个属性赋值的函数,可展开来看:
就此,回到:
整个字典转模型结束。
之上就是一个字典转模型的主干过程,整个过程还有很多细节都很赞,整个框架在安全性和效率性兼顾做的确实很棒,对于一些细节,后边再继续探索。