序文

在众多的json转model的第三方库中,YYModel可谓是表现出了优异的性能,那到底它是底层是如何实现的呢?带着这份好奇心,开启了我们读取YYModel源码之路

源码解读

YYModel类图

此图可以点击放大,查看高清图。

通过以上类图,我们可以清晰的看到了YYModel整个框架的设计,不得不说设计的非常的棒。

  • NSObjectNSArrayNSDictionary的category分别提供最上层api,以供我们json<->model;

  • YYModel协议供我们在转换过程中的个性化,包括自定义映射、集合泛型、白名单、黑名单、自定义转换过程等等;

  • _YYModelMeta_YYModelPropertyMeta 这两个类算是对YYClassInfo上层业务应用的封装,专门处理转换中的个性化(YYModel协议)

  • YYClassInfo存储了我们定义的model的信息。包括父类、父类信息、所有的方法、属性、变量。其中分别用YYClassMethodInfoYYClassPropertyInfoYYClassIvarInfo;(淋漓尽致的表现出了面向对象编程)

Json转Model

NSString *json = @"{\"uid\":123456,\"name\":\"Harry\",\"created\":\"1965-07-31T00:00:00+0000\",\"books\":[{\"name\":\"Harry Potte\",\"pages\":256}]}";
User *user = [User yy_modelWithJSON:json];
复制代码

接下来我们通过以上小小的例子正式进入源码解读。

+ (instancetype)yy_modelWithJSON:(id)json {NSDictionary *dic = [self _yy_dictionaryWithJSON:json];return [self yy_modelWithDictionary:dic];
}
复制代码

将我们的json字符串转换成NSDictionary,调用的是系统NSJSONSerialization完成的

+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {if (!dictionary || dictionary == (id)kCFNull) return nil;if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;Class cls = [self class];_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];if (modelMeta->_hasCustomClassFromDictionary) {cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;}NSObject *one = [cls new];if ([one yy_modelSetWithDictionary:dictionary]) return one;return nil;
}
复制代码

这个方法主要实现了两个功能: 1.对我们定义的Model进行解析生成_YYModelMeta,其中里边使用了缓存,使得只会解析一遍; 2.生成一个实例model,根据_YYModelMeta存储的model信息和上边生成的Dictionary对实例model复制,并且返回

赋值过程

- (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;}ModelSetContext context = {0};context.modelMeta = (__bridge void *)(modelMeta);context.model = (__bridge void *)(self);context.dictionary = (__bridge void *)(dic);if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {//第一种赋值CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);// 这里是多级keyPath映射的时候。比如//+ (NSDictionary *)modelCustomPropertyMapper {//    return @{@"desc" : @"ext.desc"};//}if (modelMeta->_keyPathPropertyMetas) {CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),ModelSetWithPropertyMetaArrayFunction,&context);}//这里是多个key对应一个属性。比如:// + (NSDictionary *)modelCustomPropertyMapper {//     return @{//              @"name" : @[@"name",@"Name",@"AName"]//              };// }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;
}
复制代码

这里是有两个赋值逻辑: 1.model需要赋值的属性是否不小于Dictionary里的数据。通过遍历数据去赋值; 2.model需要赋值的属性是否小于Dictionary里的数据,比如白名单、黑名单等个性化可以导致此情况发生。通过遍历model的属性去赋值

这里使用了两种赋值逻辑,只用第二种的话也是可以的,这样分开处理,性能提升会高一些吧

这个赋值过程主要两个核心遍历方法:

static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {ModelSetContext *context = _context;__unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);__unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];__unsafe_unretained id model = (__bridge id)(context->model);while (propertyMeta) {if (propertyMeta->_setter) {ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);}propertyMeta = propertyMeta->_next;};
}
复制代码

这个方法有个单向链表数据的遍历。其实就是一个key对应多个model属性的时候产生的。比如:

+ (NSDictionary *)modelCustomPropertyMapper { return @{ @"name":@"name", @"Firstname":@"name" }; }

static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {ModelSetContext *context = _context;__unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);__unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta);if (!propertyMeta->_setter) return;id value = nil;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) {__unsafe_unretained id model = (__bridge id)(context->model);ModelSetValueForProperty(model, value, propertyMeta);}
}
复制代码

这个方法主要分为3中情况: 1.多个key对应一个属性 2.有多急keypath进行映射 3.正常一个key对应一个属性

核心赋值

核心赋值主要通过判断各种类型进行赋值,这里做了一些兼容性转换操作等等,最主要的是通过底层objc_msgSend消息发送的方式进行赋值,所以效率极高。由于代码太长,我这里只复制一部分给大家看看,感兴趣的话可以具体看源码

static void ModelSetValueForProperty(__unsafe_unretained id model,__unsafe_unretained id value,__unsafe_unretained _YYModelPropertyMeta *meta) {if (meta->_isCNumber) {NSNumber *num = YYNSNumberCreateFromID(value);ModelSetNumberToProperty(model, num, meta);if (num) [num class]; // hold the number} else if (meta->_nsType) {if (value == (id)kCFNull) {((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);} else {switch (meta->_nsType) {case YYEncodingTypeNSString:case YYEncodingTypeNSMutableString: {if ([value isKindOfClass:[NSString class]]) {if (meta->_nsType == YYEncodingTypeNSString) {((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);} else {((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy);}} else if ([value isKindOfClass:[NSNumber class]]) {((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,meta->_setter,(meta->_nsType == YYEncodingTypeNSString) ?((NSNumber *)value).stringValue :((NSNumber *)value).stringValue.mutableCopy);} else if ([value isKindOfClass:[NSData class]]) {NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding];((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string);} else if ([value isKindOfClass:[NSURL class]]) {((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,meta->_setter,(meta->_nsType == YYEncodingTypeNSString) ?((NSURL *)value).absoluteString :((NSURL *)value).absoluteString.mutableCopy);} else if ([value isKindOfClass:[NSAttributedString class]]) {((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,meta->_setter,(meta->_nsType == YYEncodingTypeNSString) ?((NSAttributedString *)value).string :((NSAttributedString *)value).string.mutableCopy);}} break;。。。。
}
复制代码

总结

到这里整个json转model已经结束,这里没有具体分析YYModel是怎么将我们的model模型解析存储到YYClassInfo中的,这个会单独写一篇详细介绍。

通过以上代码可以看到YY的作者对代码性能的要求极高,并且大部分是C函数的调用,不用通过消息转发直接,所以整体性能肯定会高。向大神致敬!

我的博客

FlyOceanFish

iOS读YYModel源码相关推荐

  1. 看YYModel源码的一些收获

    关于源码学习自己的一些感悟 第一层:熟练使用: 第二层:读懂代码: 第三层:通晓原理: 第四层:如何设计: 自己学到了什么,还留有什么问题: 关于分享 关于线下演讲分享和线上文章分享,我一直觉得技术领 ...

  2. 【读fastclick源码有感】彻底解决tap“点透”,提升移动端点击响应速度

    前言 近期使用tap事件为老夫带来了这样那样的问题,其中一个问题是解决了点透还需要将原来一个个click变为tap,这样的话我们就抛弃了ie用户 当然可以做兼容,但是没人想动老代码的,于是今天拿出了f ...

  3. 读Zepto源码之操作DOM

    2019独角兽企业重金招聘Python工程师标准>>> 这篇依然是跟 dom 相关的方法,侧重点是操作 dom 的方法. 读Zepto源码系列文章已经放到了github上,欢迎sta ...

  4. 读Lodash源码——chunk.js

    The time is out of joint: O cursed spite, That ever I was born to set it right. --莎士比亚 最艰难的第一步 最近学习遇 ...

  5. 试读angular源码第三章:初始化zone

    直接看人话总结 前言 承接上一章 项目地址 文章地址 angular 版本:8.0.0-rc.4 欢迎看看我的类angular框架 文章列表 试读angular源码第一章:开场与platformBro ...

  6. 读spring源码(一)-ClassPathXmlApplicationContext-初始化

    工作来几乎所有的项目都用到了spring,却一直没有系统的读下源码,从头开始系统的读下吧,分章也不那么明确,读到哪里记到哪里,仅仅作为个笔记吧. 先看ClassPathXmlApplicationCo ...

  7. 读tomcat源码,随笔类图

    by yan 20170425 读tomcat源码,随笔类图:

  8. 读 zepto 源码之工具函数

    对角另一面 读 zepto 源码之工具函数 Zepto 提供了丰富的工具函数,下面来一一解读. 源码版本 本文阅读的源码为 zepto1.2.0 $.extend $.extend 方法可以用来扩展目 ...

  9. 读zepto源码之工具函数

    2019独角兽企业重金招聘Python工程师标准>>> Zepto 提供了丰富的工具函数,下面来一一解读. 源码版本 本文阅读的源码为 zepto1.2.0 $.extend $.e ...

最新文章

  1. 如何在Fragment中使用findViewById
  2. Kali Linux安全渗透教程(内部资料)
  3. shell脚本编程《linux下kvm虚拟机的创建、开启、显示、停止、重置》
  4. caffe中solver.prototxt文件参数解释
  5. Linux颜色所代表的文件类型
  6. 【python】利用python的tkinter-canvas函数绘制哆啦A梦过程详解(附源码)
  7. zend guard6的使用
  8. c语言逻辑运算符编程,C语言之逻辑运算符详解
  9. 搭建IC设计EDA虚拟机服务器,忆往昔--集成门控时钟技术的前世--分离门控时钟技术...
  10. highcharts php 动态数据,php动态传数据到highcharts的方法
  11. 【原创】编译cupcake笔记
  12. 白光LED驱动方案的选择 TPS61043
  13. setup.s (读核笔记系列)
  14. 项目管理之我见:程序开发步骤
  15. Android小游戏------猜数字
  16. 优动漫PAINT入门宝典——颜色配置实例展示
  17. 数字人民币真的来了 六年历程全回顾
  18. STI、LOD与WPE概念:形成机理及对电路设计的影响
  19. 云原生CI/CD:Tekton之trigger介绍
  20. CGI的基本定义和优劣势是什么

热门文章

  1. 【POJ3164】Command Network 最小树形图模板题 重修版
  2. Motan-远程调用的rpc框架的负载均衡策略
  3. 搜索和查询html代码,html查找框功能
  4. TexStudio使用教程
  5. TexStudio中文字体编译
  6. 数据挖掘工程师历年企业笔试真题汇总
  7. 跟面试官讲Binder(零)
  8. java searchview_搜索框(SearchView)的功能与用法
  9. 陌陌凭什么排中国iOS收入榜社交第一?无需惊奇
  10. 如何修改应用程序的图标以及exe文件的图标