前言
之前我们学习了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
中的数据转换为何种类型的对象
使用该方法指明容器中元素的类型:
objectivec
+ (NSDictionary *)modelContainerPropertyGenericClass {
}
在源码角度是这样实现的:
objectivec
// 获取容器的元素对象,存储到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是如何实现的:
先纵览一下整个流程:

具体实现过程如下:
objectivec
- (instancetype)initWithClass:(Class)cls {
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls]; //获取基础类信息(创建对象缓存类的运行时信息,包含属性列表、方法列表、父类信息等)
if (!classInfo) return nil;
self = [super init];
// Get black list
NSSet *blacklist = nil;
if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) {
NSArray *properties = [(id<YYModel>)cls modelPropertyBlacklist];
if (properties) {
blacklist = [NSSet setWithArray:properties];
}
}//黑名单处理。标记不用序列化的属性
// Get white list
NSSet *whitelist = nil;
if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) {
NSArray *properties = [(id<YYModel>)cls modelPropertyWhitelist];
if (properties) {
whitelist = [NSSet setWithArray:properties];
}
}//处理白名单,与黑名单互斥
// Get container property's generic class
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)) {
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:classInfo
propertyInfo:propertyInfo
generic: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 mapper
NSMutableDictionary *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
中:
objectivec
- (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;
}
在进行数据填充时用到了一个方法:
objectivec
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
它实际上是对dic,也就是当前JSON所对应的NSDictionary的所有元素,应用ModelSetWithDictionaryFunction
方法,并在每次调用中将context传递进去
接下来再看一下ModelSetWithDictionaryFunction
方法:
objectivec
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
objectivec
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);