当前位置: 首页 > 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.dtcms.com/a/344114.html

相关文章:

  • 飞机起落架减震筒的深孔检测方法探究 - 激光频率梳 3D 轮廓检测
  • 用户登录Token缓存Redis实践:提升SpringBoot应用性能
  • Flutter学习笔记(七)---主题
  • 嵌入式学习day34-网络-tcp/udp
  • 如何判断是否应该为了一个小功能而引入一个大体积的库
  • 配电网重构优化:以减小网损为目标的智能算法实现
  • GitLab CI :深入剖析 gl-sbom-report.cdx.json 解码“数字身份证”
  • 云蝠智能 VoiceAgent:重构售后服务场景
  • 岭回归算法拉索回归
  • LeeCode 40.组合总和II
  • 数据结构之深入探索归并排序
  • 西门子S7-1200系列基本组态常见问题
  • 【C++】多态(详解)
  • Debezium监听MySQL binlog并实现有状态重启
  • 工业环境电缆火灾预防的分布式光纤在线监测
  • 质谱数据解读
  • 【微服务的数据一致性分发问题】究极解决方案
  • Unity设置UI显示区域
  • 主题配色下的背景透明度
  • uniapp plus.io API 封装文件读写方法
  • 【IDEA2017】使用设置+创建项目的不同方式
  • GaussDB SQL引擎(1)-SQL执行流程与解析器和优化器
  • 【Qt调试】断点时,Expressions不能查看变量
  • 新手向:用FastAPI快速构建高性能Web服务
  • 单北斗变形监测系统应用指南
  • c++:MFC中sqlite3的使用(附实际案例)
  • VScode远程连接Ubuntu报错问题分析
  • 表格识别技术:通过图像处理与深度学习,将非结构化表格转化为可编辑结构化数据,推动智能化发展
  • Mac电脑英特尔版本最新系统15.6.1安装php环境
  • 机试备考笔记 18/31