当前位置: 首页 > news >正文

【iOS】YYModel第三方库源码

前言

之前我们学习了SDWebImage的源码,这篇文章来学习一下YYModel第三方库的源码

YYModel的性能优化

缓存

Model JSON转换过程中需要许多类的元数据,YYModel通过CFDictionaryCreateMutable方法将这个数据存入缓存,需要使用的时候直接拿出,就不用重复解析类

类的元数据包括:

  1. 属性信息: • 属性的名称、类型、访问权限等。 • 属性是否可选、默认值等信息。 • 特定于JSON映射的信息,例如属性与JSON键之间的映射关系。

  2. 方法信息: • 类中定义的方法,特别是设置(setter)和获取(getter)方法,这些对于属性的读写至关重要。

  3. 类型信息: • 类型信息帮助转换过程中正确处理数据类型,例如将JSON中的字符串转换为日期对象或整数。

  4. 继承信息: • 类的继承结构,了解一个类是否继承自另一个类,这在处理多态或类继承时特别重要。

JSONModel中其实也有缓存,只不过实现方式是将缓存作为关联对象存入全局map

避免KVC

JSONModel中通过KVC将相应的JSON数据赋值给属性,但如果考虑性能的话,Getter和Setter方法的性能要优于KVC,YYModel就是使用Getter和Setter方法进行赋值,性能上优越很多

尽量用纯C函数、内联函数

YYModel的实现尽量使用了纯C函数、内联函数,这样可以避免ObjC的消息发送带来的开销。例如,YYModel在处理键值映射和类型转换时,会用到如YYClassIvarInfoYYClassMethodInfo这样的C结构体来存储有关ivar和方法的信息,然后通过纯C函数来操作这些结构体,避免了频繁的ObjC方法调用

减少遍历的循环次数

在JSON和Model转换前,Model的属性个数和JSON的键值对个数都是已知的,那么这个时候选择数量比较少的那一个遍历,就可以减少循环的次数,节省很多时间。

YYModel容器类做属性

在使用容器类做为属性时,我们必须调用方法指明容器内的属性是什么类,因为容器类如 NSArrayNSDictionary类型无关的(即它们可以包含任何类型的对象)。如果不明确指定容器中元素的类型,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 看做是作者对 Runtimeobjc_ivar 结构体的封装,objc_ivarRuntime 中表示变量的结构体。

流程剖析

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);
http://www.xdnf.cn/news/1344061.html

相关文章:

  • 笔试——Day46
  • 恢复性测试:定义、重要性及实施方法
  • 深入解析CNAME记录:域名管理的隐形枢纽
  • 几个element-plus的UI,及环境配置
  • 三格电子——ModbusTCP 转 Profinet 主站网关应用实例
  • 【TrOCR】根据任务特性设计词表vocab.json
  • RabbitMQ面试精讲 Day 27:常见故障排查与分析
  • 【数据结构C语言】顺序表
  • 四十一、【高级特性篇】API 文档驱动:OpenAPI/Swagger 一键导入测试用例
  • Design Compiler:层次模型(Block Abstraction)的简介
  • memcmp 函数的使用及其模拟实现
  • 数学建模--Topsis
  • 分布式与微服务
  • [特殊字符] 潜入深渊:探索 Linux 内核源码的奇幻之旅与生存指南
  • LeetCode Hot 100 第一天
  • 相机曝光调节与自动曝光控制详解
  • AI适老服务暖人心:AI适老机顶盒破数字鸿沟、毫米波雷达护独居安全,银发生活新保障
  • 初识数据结构——Map和Set:哈希表与二叉搜索树的魔法对决
  • 车载以太网SOME/IP协议:面向服务的汽车通信技术详解
  • python-对图片中的人体换背景色
  • Java面试宝典:Redis底层原理(持久化+分布式锁)
  • 机器学习-线性回归
  • [react] class Component and function Component
  • vsCode或Cursor 使用remote-ssh插件链接远程终端
  • 用户登录Token缓存Redis实践:提升SpringBoot应用性能
  • yggjs_rlayout使用教程 v0.1.0
  • unistd.h 常用函数速查表
  • 【Linux仓库】进程的“夺舍”与“飞升”:exec 驱动的应用现代化部署流水线
  • Elasticsearch倒排索引和排序
  • Elasticsearch核心概念