YYModel的深入理解
文件结构
相信使用过MJExtensions的iOS开发者来说,接触YYModel并不是很陌生,因为两者在某些思路上是相似的,例如都用了category的方式来实现,Protocol中的方法都有着相似的功能,例如黑白名单、Property名称和dictionary中key中的对应关系等。YYModel的文件包括:
- YYModel.h
- YYClassInfo.h、YYClassInfo.m
- NSObject+YYModel.h、NSObject+YYModel.m
其中YYModel.h是公共头文件、YYClassInfo是针对NSObject的Ivar、Property、Method进行封装的类、NSObject+YYModel是具体负责转JSON和model的category。本篇分析一下YYClassInfo。
YYClassInfo
查看代码发现,NSObject对象中定义一个指针isa,如下:
@interface NSObject <NSObject> {Class isa OBJC_ISA_AVAILABILITY;
}
isa指向Class类型,Class类型是objc_class的别名,而objc_class是一个结构体,如下:
struct objc_class {Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__Class super_class OBJC2_UNAVAILABLE;const char *name OBJC2_UNAVAILABLE;long version OBJC2_UNAVAILABLE;long info OBJC2_UNAVAILABLE;long instance_size OBJC2_UNAVAILABLE;struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;struct objc_method_list **methodLists OBJC2_UNAVAILABLE;struct objc_cache *cache OBJC2_UNAVAILABLE;struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
主要信息包含:isa指向Class的meta类、super_class指向Class的父类。具体关系可以参考下图:
name是名称、objc_ivar_list是成员变量链表、methodLists是成员方法链表、protocols是附属协议链表。关于Class结构和runtime的文档,可以参考苹果官方文档和http://www.cocoachina.com/ios/20141031/10105.html。
YYClassInfo封装了Class对象的各个元素,代码如下:
@interface YYClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls; //关联的Class对象
@property (nullable, nonatomic, assign, readonly) Class superCls; //父类
@property (nullable, nonatomic, assign, readonly) Class metaCls; //元类
@property (nonatomic, readonly) BOOL isMeta; //是否是元类
@property (nonatomic, strong, readonly) NSString *name; //Class名称
@property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; //父类Class对应的YYClassInfo
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; //ivar键值对
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; //方法键值对
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; //附属协议键值对
@end
YYClassInfo对象通过classInfoWithClass:方法根据Class创建一个YYClassInfo对象。代码注释如下:
+ (instancetype)classInfoWithClass:(Class)cls {if (!cls) return nil;static CFMutableDictionaryRef classCache;static CFMutableDictionaryRef metaCache;static dispatch_once_t onceToken;static dispatch_semaphore_t lock;dispatch_once(&onceToken, ^{ //创建键值对classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);lock = dispatch_semaphore_create(1);});dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));if (info && info->_needUpdate) {[info _update];}dispatch_semaphore_signal(lock);if (!info) {info = [[YYClassInfo alloc] initWithClass:cls]; //创建YYClassInfo对象if (info) { //存入缓存dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));dispatch_semaphore_signal(lock);}}return info;
}
该方法用了一个键值对来存储创建好的YYClassInfo对象,key是Class对象,value是classInfo对象,当多次调用classInfoWithClass:方法时,如果Class对象相同,则直接从缓存中取出classInfo对象,不用每次都创建一个新的classInfo对象,提高了性能。同时检查_needUpdate变量,如有,刷新classInfo对象的内部属性值。
_needUpdate是YYClassInfo的一个成员变量,当Class的信息更新时,设置_needUpdate为YES,表示需要更新YYClassInfo。
- (void)setNeedUpdate { //设置需要更新_needUpdate = YES;
}
- (BOOL)needUpdate { //返回是否需要更新return _needUpdate;
}
如果_needUpdate设置为YES,则调用_update方法进行更新。第一次创建YYClassInfo对象时,会去调用一次_update方法,_update方法负责构建YYClassInfo中的ivarInfos、methodInfos和propertyInfos,这三个键值对分别维护了YYClassIvarInfo、YYClassMethodInfo、YYClassPropertyInfo信息,代码注释如下:
- (void)_update {_ivarInfos = nil;_methodInfos = nil;_propertyInfos = nil;Class cls = self.cls;unsigned int methodCount = 0;Method *methods = class_copyMethodList(cls, &methodCount); //获取Method数组if (methods) {NSMutableDictionary *methodInfos = [NSMutableDictionary new];_methodInfos = methodInfos;for (unsigned int i = 0; i < methodCount; i++) {YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]]; //用YYClassMethodInfo封装Method中的信息if (info.name) methodInfos[info.name] = info; //存入methodInfos}free(methods);}unsigned int propertyCount = 0;objc_property_t *properties = class_copyPropertyList(cls, &propertyCount); //获取objc_property_t数组if (properties) {NSMutableDictionary *propertyInfos = [NSMutableDictionary new];_propertyInfos = propertyInfos;for (unsigned int i = 0; i < propertyCount; i++) {YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]]; //用YYClassPropertyInfo封装objc_property_t中的信息if (info.name) propertyInfos[info.name] = info; //存入propertyInfos}free(properties);}unsigned int ivarCount = 0;Ivar *ivars = class_copyIvarList(cls, &ivarCount); //获取Ivar数组if (ivars) {NSMutableDictionary *ivarInfos = [NSMutableDictionary new];_ivarInfos = ivarInfos;for (unsigned int i = 0; i < ivarCount; i++) {YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]]; //用YYClassIvarInfo封装Ivar中的信息if (info.name) ivarInfos[info.name] = info; //存入ivarInfos}free(ivars);}if (!_ivarInfos) _ivarInfos = @{};if (!_methodInfos) _methodInfos = @{};if (!_propertyInfos) _propertyInfos = @{};_needUpdate = NO;
}
例如定义一个Student的类,如下:
@interface College : NSObject
@property (nonatomic, copy) NSString *name;
@end@interface Student : NSObject <YYModel>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) College *college;
@endStudent *student = [[Student alloc] init];
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:[student class]];
通过YYClassInfo的初始化方法classInfoWithClass创建,得到对象如下:
下面分析一下ivarInfos、methodInfos和propertyInfos中存储的对象:
YYClassIvarInfo
通过runtime的class_copyIvarList方法得到Ivar数组, Ivar是iOS中描述成员变量的数据结构,如下:
typedef struct objc_ivar *Ivar;struct objc_ivar {char *ivar_name OBJC2_UNAVAILABLE; //成员变量名称char *ivar_type OBJC2_UNAVAILABLE; //成员变量类型int ivar_offset OBJC2_UNAVAILABLE; //成员变量偏移值#ifdef __LP64__int space OBJC2_UNAVAILABLE;#endif}
用YYClassIvarInfo来封装Ivar中的信息,如下:
@interface YYClassIvarInfo : NSObject@property (nonatomic, assign, readonly) Ivar ivar; //关联的Ivar变量@property (nonatomic, strong, readonly) NSString *name; //成员变量名@property (nonatomic, assign, readonly) ptrdiff_t offset; //成员变量偏移值@property (nonatomic, strong, readonly) NSString *typeEncoding; //成员变量类型编码@property (nonatomic, assign, readonly) YYEncodingType type; //类型枚举@end
initWithIvar:方法通过Ivar来构建YYClassIvarInfo对象,注释如下:
- (instancetype)initWithIvar:(Ivar)ivar {if (!ivar) return nil;self = [super init];_ivar = ivar; //关联Ivar对象const char *name = ivar_getName(ivar); //获取名称if (name) {_name = [NSString stringWithUTF8String:name];}_offset = ivar_getOffset(ivar); //获取偏移值const char *typeEncoding = ivar_getTypeEncoding(ivar); //获取Ivar的类型编码if (typeEncoding) { //如果有类型编码_typeEncoding = [NSString stringWithUTF8String:typeEncoding];_type = YYEncodingGetType(typeEncoding); //根据类型编码查找对应类型枚举}return self;}
typeEncoding是iOS规定的一套类型编码,用不同的字符表示不同的类型,例如@表示对象类型,B表示bool类型,具体参考苹果官方文档。通过YYEncodingGetType方法可以通过类型编码映射到对应的枚举。例如student的成员变量name对应如下:
YYClassMethodInfo
通过class_copyMethodList方法得到Method数组,Method是iOS中描述成员方法数据结构objc_method,如下:
struct objc_method {SEL method_name OBJC2_UNAVAILABLE; //方法名char *method_types OBJC2_UNAVAILABLE; //方法参数IMP method_imp OBJC2_UNAVAILABLE; //方法的实现 }
里面主要包含一个SEL和IMP,SEL是方法的标识符,IMP是真正实现方法的函数指针,OC在调用对象的方法时,转化如下:
[student mark]; ->转化为objc_msgSend(id, SEL, ...) //第一个参数是student对象,第二个参数是SEL类型的@selector(mark),后续参数是mark方法要传的参数。 ->遍历Class对象的method数组,通过selector找到相应Method对应的IMP,调用IMP,并将参数传入。
用YYClassMethodInfo来封装Method中的信息,如下:
@interface YYClassMethodInfo : NSObject @property (nonatomic, assign, readonly) Method method; //关联的Method变量 @property (nonatomic, strong, readonly) NSString *name; //成员方法名 @property (nonatomic, assign, readonly) SEL sel; //SEL标识符 @property (nonatomic, assign, readonly) IMP imp; //IMP指针 @property (nonatomic, strong, readonly) NSString *typeEncoding;//类型编码 @property (nonatomic, strong, readonly) NSString *returnTypeEncoding; //方法返回类型编码 @property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *argumentTypeEncodings; //方法参数类型编码数组 @end
initWithMethod:方法通过Method构建YYClassMethodInfo,代码注释如下:
- (instancetype)initWithMethod:(Method)method {if (!method) return nil;self = [super init];_method = method; //关联Method_sel = method_getName(method); //获取SEL_imp = method_getImplementation(method); //获取IMPconst char *name = sel_getName(_sel); //获取方法名if (name) {_name = [NSString stringWithUTF8String:name];}const char *typeEncoding = method_getTypeEncoding(method); //获取类型编码if (typeEncoding) {_typeEncoding = [NSString stringWithUTF8String:typeEncoding];}char *returnType = method_copyReturnType(method); //方法返回类型if (returnType) {_returnTypeEncoding = [NSString stringWithUTF8String:returnType];free(returnType);}...return self; }
由于[a bb:cc]在运行时会被转化为objc_msgSend(a, @selector(bb),cc),第一个参数表示发送消息的目的对象,第二个参数表示消息的SEL标识,第三个参数为入参cc。所以Method的typeEncoding除了包含返回值类型和bb:方法的参数类型,还多包含前两个参数。
例如Student的name的setter方法,如下:
typeEncoding共4个参数,第一个编码是"v"(返回值是void类型)、第二个编码是"@"(发送消息的目的对象是NSObject类型),第三个编码是":"(类型是SEL),第四个是编码是"@"(setName:的参数是NSString类型)。
YYClassPropertyInfo
通过class_copyPropertyList方法得到objc_property_t类型的数组,objc_property_t是描述Class对象property属性的结构,YYClassPropertyInfo封装objc_property_t相关信息。
@interface YYClassPropertyInfo : NSObject @property (nonatomic, assign, readonly) objc_property_t property; //关联的objc_property_t @property (nonatomic, strong, readonly) NSString *name; //property名 @property (nonatomic, assign, readonly) YYEncodingType type; //property的类型 @property (nonatomic, strong, readonly) NSString *typeEncoding; //类型编码 @property (nonatomic, strong, readonly) NSString *ivarName; //Ivar名 @property (nullable, nonatomic, assign, readonly) Class cls; // @property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *protocols; // @property (nonatomic, assign, readonly) SEL getter; //getter方法对应的SEL @property (nonatomic, assign, readonly) SEL setter; //setter方法对应的SEL @end
initWithProperty:方法通过objc_property_t创建YYClassPropertyInfo对象,代码注释如下:
- (instancetype)initWithProperty:(objc_property_t)property {..._property = property; //关联objc_property_tconst char *name = property_getName(property); //获得property名称if (name) {_name = [NSString stringWithUTF8String:name];}YYEncodingType type = 0;unsigned int attrCount;objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount); //获取property的参数数组,//遍历attrs数组,获取相关信息,例如for (unsigned int i = 0; i < attrCount; i++) {switch (attrs[i].name[0]) { case 'T': {...} break; //T对应typeEncodingcase 'V': { //V对应ivar值if (attrs[i].value) {_ivarName = [NSString stringWithUTF8String:attrs[i].value];}} break;... //"R、C、&、N"等property的修饰器类型}} }
通过property_copyAttributeList方法获取attrs数组,每个元素是objc_property_attribute_t类型的结构,如下:
typedef struct {const char *name; //标识const char *value; //值 } objc_property_attribute_t;
name的标识不同,例如T、V等,对应的value有不同含义。例如Student的name属性,如下:
_typeEncoding的前缀是@,因为name是NSString类型,注意这里的_name表示property的名称,而_ivarName表示property对应的成员变量的名称,_getter和_setter分别是property生成的方法。
YYModel的深入理解相关推荐
- 看YYModel源码的一些收获
关于源码学习自己的一些感悟 第一层:熟练使用: 第二层:读懂代码: 第三层:通晓原理: 第四层:如何设计: 自己学到了什么,还留有什么问题: 关于分享 关于线下演讲分享和线上文章分享,我一直觉得技术领 ...
- YYModel的源码读后感
在项目的开发中,相信很多开发者都有json映射成Model的需求.在没有接触YYModel之前,我都是用自己写的转换类来映射.主要是通过runtime库,获取对象的属性列表,继而给需要的类的属性赋值. ...
- iOS高性能Model转换框架----YYModel学习
YYWebImage源码分析 YYImage源码 YYText源码分析 框架简介 YYClassIvarInfo 此类就是objc_ivar的封装 /**Instance variable infor ...
- YYModel底层解析- Runtime
这段时间一直在忙新的需求,没有时间来整理代码,发表自己技术博客,今天我们来看一下YYModel的底层解析以及如何使用,希望对大家有所帮助! 一 概述 概括 YYModel是一个轻量级的JSON模型转换 ...
- 史上最全YYModel的使用详解
原文链接:http://www.jianshu.com/p/25e678fa43d3 demo链接:https://github.com/walkertop/YYModel---Demo 插件链接:h ...
- YYModel源码解读
YYModel 是一个非常优秀的数据转模型,模型转字典与JSON串的库.而且代码量非常少.可见作者架构,抽象,对OC理解已经到了一个很高的高度.希望站在巨人的肩膀上有一天自己也能达到. YYModel ...
- YYModel解析2
主体分层 YYClassInfo主要分为以下几部分: typedef NS_OPTIONS(NSUInteger, YYEncodingType)与YYEncodingType YYEncodingG ...
- YYModel V1.0.4源码解析
YYKit出现了很长时间了,一直想要详细解析一下它的源码,都是各种缘由推迟了. 最近稍微闲了一点,决定先从最简单的YYModel开始吧. 首先,我也先去搜索了一下YYModel相关的文章,解析主要AP ...
- 通用解题法——回溯算法(理解+练习)
积累算法经验,积累解题方法--回溯算法,你必须要掌握的解题方法! 什么是回溯算法呢? 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就&quo ...
最新文章
- 告别2019:属于深度学习的十年,那些我们必须知道的经典
- QT的QQmlContext类的使用
- python怎么打印字典_在python中打印字典的原始输入顺序
- 3.13 判读是否是对称素数
- 【HDU - 5878】I Count Two Three(打表)
- apache poi excel显示 base64 图片_数据处理之带图片Excel数据处理解惑
- EMLOG复制网站文字提醒弹窗源码美化版
- 6_less中的匹配模式
- python符号格式化设置区间_[Python3 填坑] 001 格式化符号 格式化操作符的辅助指令...
- jQuery Howto: 如何快速创建一个AJAX的加载的图片效果
- oracle对象类型_如何创建Oracle类型对象
- 编程科普书籍推荐(Java)
- Hibernate视频学习笔记(8)Lazy策略
- linux内核的reciprocal_value结构体
- 石油化工企业防雷工程和防雷接地应用方案
- [MdSQL]表的增删查改(进阶)
- 机器学习算法之集成方法
- 建立RADIUS认证服务器
- boj489. 小妹妹去划船
- matlab投资案例,组合投资的风险与收益及其MATLAB的实现..doc
热门文章
- 偶然一次机会对xposed插件学习记录(微信自动抢红包原理研究,适配微信8.0)
- 【商业分析项目1】新店选址分析 (数据源已脱敏)
- 看看最近京东哪些产品最火,Python爬取京东的商品排行
- JAVA开发学习路线及书籍
- 由浅入深理解latent diffusion/stable diffusion(2):扩散生成模型的工作原理
- 店铺标牌识别【卷积神经网络】
- 菜菜学C++之标准模板库(STL库)
- Redux-Saga在React工程架构之的应用实践详解
- npm安装模块到全局(win10)
- 2019 计蒜之道 复赛 D——“星云系统”(单调栈||队列+贪心)