文件结构

​ 相信使用过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中存储的对象:

  1. 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对应如下:

  1. 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类型)。

  1. 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的深入理解相关推荐

  1. 看YYModel源码的一些收获

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

  2. YYModel的源码读后感

    在项目的开发中,相信很多开发者都有json映射成Model的需求.在没有接触YYModel之前,我都是用自己写的转换类来映射.主要是通过runtime库,获取对象的属性列表,继而给需要的类的属性赋值. ...

  3. iOS高性能Model转换框架----YYModel学习

    YYWebImage源码分析 YYImage源码 YYText源码分析 框架简介 YYClassIvarInfo 此类就是objc_ivar的封装 /**Instance variable infor ...

  4. YYModel底层解析- Runtime

    这段时间一直在忙新的需求,没有时间来整理代码,发表自己技术博客,今天我们来看一下YYModel的底层解析以及如何使用,希望对大家有所帮助! 一 概述 概括 YYModel是一个轻量级的JSON模型转换 ...

  5. 史上最全YYModel的使用详解

    原文链接:http://www.jianshu.com/p/25e678fa43d3 demo链接:https://github.com/walkertop/YYModel---Demo 插件链接:h ...

  6. YYModel源码解读

    YYModel 是一个非常优秀的数据转模型,模型转字典与JSON串的库.而且代码量非常少.可见作者架构,抽象,对OC理解已经到了一个很高的高度.希望站在巨人的肩膀上有一天自己也能达到. YYModel ...

  7. YYModel解析2

    主体分层 YYClassInfo主要分为以下几部分: typedef NS_OPTIONS(NSUInteger, YYEncodingType)与YYEncodingType YYEncodingG ...

  8. YYModel V1.0.4源码解析

    YYKit出现了很长时间了,一直想要详细解析一下它的源码,都是各种缘由推迟了. 最近稍微闲了一点,决定先从最简单的YYModel开始吧. 首先,我也先去搜索了一下YYModel相关的文章,解析主要AP ...

  9. 通用解题法——回溯算法(理解+练习)

    积累算法经验,积累解题方法--回溯算法,你必须要掌握的解题方法! 什么是回溯算法呢? 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就&quo ...

最新文章

  1. 告别2019:属于深度学习的十年,那些我们必须知道的经典
  2. QT的QQmlContext类的使用
  3. python怎么打印字典_在python中打印字典的原始输入顺序
  4. 3.13 判读是否是对称素数
  5. 【HDU - 5878】I Count Two Three(打表)
  6. apache poi excel显示 base64 图片_数据处理之带图片Excel数据处理解惑
  7. EMLOG复制网站文字提醒弹窗源码美化版
  8. 6_less中的匹配模式
  9. python符号格式化设置区间_[Python3 填坑] 001 格式化符号 格式化操作符的辅助指令...
  10. jQuery Howto: 如何快速创建一个AJAX的加载的图片效果
  11. oracle对象类型_如何创建Oracle类型对象
  12. 编程科普书籍推荐(Java)
  13. Hibernate视频学习笔记(8)Lazy策略
  14. linux内核的reciprocal_value结构体
  15. 石油化工企业防雷工程和防雷接地应用方案
  16. [MdSQL]表的增删查改(进阶)
  17. 机器学习算法之集成方法
  18. 建立RADIUS认证服务器
  19. boj489. 小妹妹去划船
  20. matlab投资案例,组合投资的风险与收益及其MATLAB的实现..doc

热门文章

  1. 偶然一次机会对xposed插件学习记录(微信自动抢红包原理研究,适配微信8.0)
  2. 【商业分析项目1】新店选址分析 (数据源已脱敏)
  3. 看看最近京东哪些产品最火,Python爬取京东的商品排行
  4. JAVA开发学习路线及书籍
  5. 由浅入深理解latent diffusion/stable diffusion(2):扩散生成模型的工作原理
  6. 店铺标牌识别【卷积神经网络】
  7. 菜菜学C++之标准模板库(STL库)
  8. Redux-Saga在React工程架构之的应用实践详解
  9. npm安装模块到全局(win10)
  10. 2019 计蒜之道 复赛 D——“星云系统”(单调栈||队列+贪心)