【iOS】YYModel第三方库源码
前言
之前我们学习了SDWebImage的源码,这篇文章来学习一下YYModel第三方库的源码
YYModel的性能优化
缓存
Model JSON转换过程中需要许多类的元数据,YYModel通过CFDictionaryCreateMutable
方法将这个数据存入缓存,需要使用的时候直接拿出,就不用重复解析类
类的元数据包括:
属性信息: • 属性的名称、类型、访问权限等。 • 属性是否可选、默认值等信息。 • 特定于JSON映射的信息,例如属性与JSON键之间的映射关系。
方法信息: • 类中定义的方法,特别是设置(setter)和获取(getter)方法,这些对于属性的读写至关重要。
类型信息: • 类型信息帮助转换过程中正确处理数据类型,例如将JSON中的字符串转换为日期对象或整数。
继承信息: • 类的继承结构,了解一个类是否继承自另一个类,这在处理多态或类继承时特别重要。
JSONModel中其实也有缓存,只不过实现方式是将缓存作为关联对象存入全局map
避免KVC
JSONModel中通过KVC将相应的JSON数据赋值给属性,但如果考虑性能的话,Getter和Setter方法的性能要优于KVC,YYModel就是使用Getter和Setter方法进行赋值,性能上优越很多
尽量用纯C函数、内联函数
YYModel的实现尽量使用了纯C函数、内联函数,这样可以避免ObjC的消息发送带来的开销。例如,YYModel
在处理键值映射和类型转换时,会用到如YYClassIvarInfo
、YYClassMethodInfo
这样的C结构体来存储有关ivar和方法的信息,然后通过纯C函数来操作这些结构体,避免了频繁的ObjC方法调用
减少遍历的循环次数
在JSON和Model转换前,Model的属性个数和JSON的键值对个数都是已知的,那么这个时候选择数量比较少的那一个遍历,就可以减少循环的次数,节省很多时间。
YYModel容器类做属性
在使用容器类做为属性时,我们必须调用方法指明容器内的属性是什么类,因为容器类如 NSArray
或 NSDictionary
是类型无关的(即它们可以包含任何类型的对象)。如果不明确指定容器中元素的类型,YYModel
无法决定将 JSON
中的数据转换为何种类型的对象
使用该方法指明容器中元素的类型:
+ (NSDictionary *)modelContainerPropertyGenericClass {
}
在源码角度是这样实现的:
// 获取容器的元素对象,存储到genericMapper key为属性名 value为该容器类里面元素的类型
NSDictionary *genericMapper = nil;
if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {// 调用类方法获取泛型映射字典genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass];if (genericMapper) {// 创建临时可变字典用于存储处理后的键值对NSMutableDictionary *tmp = [NSMutableDictionary new];[genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {// 确保键是字符串类型(属性名)if (![key isKindOfClass:[NSString class]]) return;// 获取对象的类(元类)Class meta = object_getClass(obj);if (!meta) return;// 如果对象是类对象(元类),直接使用if (class_isMetaClass(meta)) {// key为属性名 value为该容器类里面元素的类型tmp[key] = obj;} // 如果对象是字符串类型,尝试将其转换为类else if ([obj isKindOfClass:[NSString class]]) {Class cls = NSClassFromString(obj);if (cls) {tmp[key] = cls;}}}];// 使用处理后的字典替换原始字典genericMapper = tmp;}
}
架构分析
YYModel第三方库中有这几个文件,可以看到数量很少,是一个十分轻量级的模型转换库
显而易见可以把这些文件分为两个模块:
YYClassInfo
主要将Runtime 层级的一些结构体封装到NSObject
层级以便调用。NSObject+YYModel
主要负责处理转换的逻辑以及提供接口 这里面转换的逻辑基本上都是用到了YYClassInfo
中封装的Runtime
结构体
YYClassInfo剖析
可以看出来YYClassInfo的结构,正如刚刚所说他是底层Runtime结构体的封装,把这些结构体封装到NSObject层级可以方便调用,这样就简化了代码处理的逻辑。
我们用几个例子来直观地看一看YYModel对这些结构体的封装:
YYClassIvarInfo
看做是作者对 Runtime
层 objc_ivar
结构体的封装,objc_ivar
是 Runtime
中表示变量的结构体。
流程剖析
JSON统一成NSDictionary
YYModel的核心功能就是把JSON格式的数据转换为Model,接下来我们就看看它是如何把JSON转换为Model的,从yy_modelWithJSON方法开始,它将JSON转化为Model:
可以看到他的实现是先把JSON转换成字典,再把字典转换为Model。那么先看看怎么把JSON转换为字典:
将NSDictionary 转换为Model对象
这一步通过方法yy_modelWithDictionary完成,先概括一下这个方法中干了三件事:
提取出Model类中的所有属性
创建一个元数组,用来存放属性中的各种信息,这样等后面验证类型等操作时就可以直接根据元数组验证
将JSON数据映射到Model对象中
提取Model信息
提取Model信息通过YYModelMeta *modelMeta = [YYModelMeta metaWithClass:cls]; 这行代码实现,要看懂这个方法,首先先看一下_YYModelMeta这个类
从属性的名字和注释中可以看出来,这个类中包含了属性的信息,还包含了属性与key之间的映射关系,因此我们可以明确字典中的每个key
对应的value
属性,因而可以正确将NSDictionary
中的值映射到Model
属性上。
接下来看metaWithClass的源码:
从源码中可以看到最后调用了一个initWithClass进行元数组的创建,我们接着看看initWithClass是如何实现的:
先纵览一下整个流程:
具体实现过程如下:
- (instancetype)initWithClass:(Class)cls {YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls]; //获取基础类信息(创建对象缓存类的运行时信息,包含属性列表、方法列表、父类信息等)if (!classInfo) return nil;self = [super init];// Get black listNSSet *blacklist = nil;if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) {NSArray *properties = [(id<YYModel>)cls modelPropertyBlacklist];if (properties) {blacklist = [NSSet setWithArray:properties];}}//黑名单处理。标记不用序列化的属性// Get white listNSSet *whitelist = nil;if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) {NSArray *properties = [(id<YYModel>)cls modelPropertyWhitelist];if (properties) {whitelist = [NSSet setWithArray:properties];}}//处理白名单,与黑名单互斥// Get container property's generic classNSDictionary *genericMapper = nil;//容器泛型处理if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass]; //获取原始映射if (genericMapper) {NSMutableDictionary *tmp = [NSMutableDictionary new];[genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {if (![key isKindOfClass:[NSString class]]) return;Class meta = object_getClass(obj);if (!meta) return;//过滤无效项if (class_isMetaClass(meta)) {tmp[key] = obj; //标准化映射 类对象直接存储} else if ([obj isKindOfClass:[NSString class]]) {Class cls = NSClassFromString(obj);if (cls) {tmp[key] = cls; //标准化映射 类名字符串转换}}}];genericMapper = tmp;}}// Create all property metas.NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];YYClassInfo *curClassInfo = classInfo;//属性元数据收集while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy)for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) {if (!propertyInfo.name) continue;if (blacklist && [blacklist containsObject:propertyInfo.name]) continue;//跳过黑名单if (whitelist && ![whitelist containsObject:propertyInfo.name]) continue;//跳过白名单_YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfopropertyInfo:propertyInfogeneric:genericMapper[propertyInfo.name]]; //创建属性元数据if (!meta || !meta->_name) continue;if (!meta->_getter || !meta->_setter) continue;if (allPropertyMetas[meta->_name]) continue;allPropertyMetas[meta->_name] = meta;}curClassInfo = curClassInfo.superClassInfo; //向上遍历父类}if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy;// create mapperNSMutableDictionary *mapper = [NSMutableDictionary new];NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];//自定义属性映射处理if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];[customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {_YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];if (!propertyMeta) return;[allPropertyMetas removeObjectForKey:propertyName];if ([mappedToKey isKindOfClass:[NSString class]]) {if (mappedToKey.length == 0) return;propertyMeta->_mappedToKey = mappedToKey;NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."];for (NSString *onePath in keyPath) {if (onePath.length == 0) {NSMutableArray *tmp = keyPath.mutableCopy;[tmp removeObject:@""];keyPath = tmp;break;}}if (keyPath.count > 1) {propertyMeta->_mappedToKeyPath = keyPath;[keyPathPropertyMetas addObject:propertyMeta];}propertyMeta->_next = mapper[mappedToKey] ?: nil;mapper[mappedToKey] = propertyMeta; //构建最终映射字典} else if ([mappedToKey isKindOfClass:[NSArray class]]) {NSMutableArray *mappedToKeyArray = [NSMutableArray new];for (NSString *oneKey in ((NSArray *)mappedToKey)) {if (![oneKey isKindOfClass:[NSString class]]) continue;if (oneKey.length == 0) continue;NSArray *keyPath = [oneKey componentsSeparatedByString:@"."];if (keyPath.count > 1) {[mappedToKeyArray addObject:keyPath];} else {[mappedToKeyArray addObject:oneKey];}if (!propertyMeta->_mappedToKey) {propertyMeta->_mappedToKey = oneKey;propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : nil;}}if (!propertyMeta->_mappedToKey) return;propertyMeta->_mappedToKeyArray = mappedToKeyArray;[multiKeysPropertyMetas addObject:propertyMeta];propertyMeta->_next = mapper[mappedToKey] ?: nil;//通过 _next 指针处理多属性映射到同 key 的情况mapper[mappedToKey] = propertyMeta;}}];}[allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {propertyMeta->_mappedToKey = name;propertyMeta->_next = mapper[name] ?: nil;mapper[name] = propertyMeta;}];if (mapper.count) _mapper = mapper;if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas;if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas;_classInfo = classInfo;_keyMappedCount = _allPropertyMetas.count;_nsType = YYClassGetNSType(cls);_hasCustomWillTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomWillTransformFromDictionary:)]);_hasCustomTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformFromDictionary:)]);_hasCustomTransformToDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformToDictionary:)]);_hasCustomClassFromDictionary = ([cls respondsToSelector:@selector(modelCustomClassForDictionary:)]);return self;
}
使用NSDictionary的数据填充Model
我们在前面已经创建了_YYModelMeta(存放属性元数据的容器),在这之中我们完成了属性黑白名单的过滤,以及属性名和JSON中字段名的对应关系
接下来我们就可以使用Model 类创建出一个Model,并从JSON (NSDictionary)中取出对应的值,对Model对象进行填充,最后再将生成的model对象返回就完成了整个序列化过程,这个过程在yy_modelSetWithDictionary
中:
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {if (!dic || dic == (id)kCFNull) return NO;if (![dic isKindOfClass:[NSDictionary class]]) return NO;//参数校验与元数据准备
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];if (modelMeta->_keyMappedCount == 0) return NO;if (modelMeta->_hasCustomWillTransformFromDictionary) {dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];if (![dic isKindOfClass:[NSDictionary class]]) return NO;}//构建context 上下文中包括modelMeta model的各种映射信息,model 要填充的model对象, dictionary 包含数据的字典ModelSetContext context = {0};context.modelMeta = (__bridge void *)(modelMeta);//context.modelMeta存放了属性元数据context.model = (__bridge void *)(self);context.dictionary = (__bridge void *)(dic);//context.dictionary中存放了JSON数据//根据效率选择遍历方式//开始将dictionary数据填充到model上,这里最关键的就是ModelSetWithPropertyMetaArrayFunction方法。if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);if (modelMeta->_keyPathPropertyMetas) {CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),ModelSetWithPropertyMetaArrayFunction,&context);}if (modelMeta->_multiKeysPropertyMetas) {CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),ModelSetWithPropertyMetaArrayFunction,&context);}} else {CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,CFRangeMake(0, modelMeta->_keyMappedCount),ModelSetWithPropertyMetaArrayFunction,&context);}if (modelMeta->_hasCustomTransformFromDictionary) {return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];}return YES;
}
在进行数据填充时用到了一个方法:
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
它实际上是对dic,也就是当前JSON所对应的NSDictionary的所有元素,应用ModelSetWithDictionaryFunction
方法,并在每次调用中将context传递进去
接下来再看一下ModelSetWithDictionaryFunction
方法:
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {ModelSetContext *context = _context;// 取出字典封装的JSON数据__unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);__unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta);// 取出meta属性,目的是为了得到键与setter方法等//_propertyMeta:指向 _YYModelPropertyMeta 的指针,这是一个封装了属性相关信息(如映射的 JSON 键名、键路径、访问器等)的结构体。重要的是存放了映射的JSON键名,这里是通过先前初始化_propertyMeta实现的if (!propertyMeta->_setter) return;id value = nil;//通过属性信息里面的key的映射关系拿到字典里面对应的value值if (propertyMeta->_mappedToKeyArray) {value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);} else if (propertyMeta->_mappedToKeyPath) {value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);} else {value = [dictionary objectForKey:propertyMeta->_mappedToKey];}if (value) {// 取出模型设置value__unsafe_unretained id model = (__bridge id)(context->model);ModelSetValueForProperty(model, value, propertyMeta);}
}
最后在ModelSetValueForProperty中使用消息发送objc_msgSend
通过setter方法设置value
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);