YYModel源码详细解析-1
2016.06.18 01:59* 字数 3167 阅读 3800评论 12喜欢 37
前言:
阅读YYModel之前建议先阅读Runtime基础篇,YYModel采用Runtime直接调用 Getter/Setter,是一款高性能 iOS/OSX 模型转换框架,支持定义映射过程。正好最近想深入Runtime一番,于是就读了几遍YYModel的源码,本文将从作者提供的性能优化的几个 Tip,还有结合Github 上的 Issues,以及相关项目线索为起点对YYModel源码进行分析,并且绘制一张项目的整体架构图,设计的思想。
源码来源:
资料参考文献
项目整体架构:
项目图
Paste_Image.png
Paste_Image.png
Paste_Image.png
Paste_Image.png
Paste_Image.png
![Uploading Paste_Image_669357.png . . .]
Paste_Image.png
Paste_Image.png
Paste_Image.png
Paste_Image.png
类与对象的继承层次关系如图1-1
图1-1 Google source
YYModel层次关系
YYClassInfo层次关系
YYClassInfo设计图
注意:所指定的方向是包含关系,例如YYClassInfo包含class,同时YYClassInfo又包含YYClassMethodInfo、YYClassPropertyInfo、YYClassIvarInfo,其实用继承关系理解是最好,这样可以和类与关系图思想来理解更好(所以下面的图,就画成继承关系,便于结合类与关系继承理解),但是准确来说是包含关系。
YYClassIvarInfo 对Runtime Ivar进行封装
YYClassMethodInfo 对Runtime Method进行封装
YYClassPropertyInfo 对Runtime Property进行封装
YYClassInfo 对YYClassIvarInfo、YYClassMethodInfo、YYClassPropertyInfo、以及Runtime Class进行封装
_YYModelPropertyMeta层次关系
_YYModelPropertyMeta层次关系可以分为两种,一种是_YYModelPropertyMeta对YYClassInfo的封装,另一种是_YYModelPropertyMeta对YYClassPropertyInfo的封装。
** _YYModelPropertyMeta对YYClassInfo封装**
Class包含关系图
Paste_Image.png
Paste_Image.png
Paste_Image.png
Paste_Image.png
每一份Class都有可能嵌套Class,那么Class就有可能要实现多级类映射Class关系,而且_YYModelPropertyMeta提供了_next指针便于指向上一级属性映射关系
YYClassInfo类提供_superCls遍历父类class信息,后面会详细说明
_YYModelPropertyMeta包含YYClassInfo信息
** _YYModelPropertyMeta对YYClassPropertyInfo封装**
_YYModelPropertyMeta设计图1
每一份Class都有可能嵌套Class,,那么Class就有可能要实现多级类映射Property关系,而且YYModelProperyMeta提供_mappedToKeyArray、_mappedToKeyPath属性来判断多级属性映射关系
YYClassInfo.h
类型编码
YYClassInfo提供了三种枚举类型分别是Foundation Framework 编码、 Qualifier限定符编码、 Property属性编码其对应类型编码文档Table 6-1、Table 6-2、Table 7-1.
typedefNS_OPTIONS(NSUInteger, YYEncodingType) {typedefNS_OPTIONS(NSUInteger, YYEncodingType) {// Table 6-1 Foundation Framework 编码YYEncodingTypeMask =0xFF,///< mask of type value 表示低8位的十六进制Mask掩码,用于得到枚举值的低8位值(0 --> 8位),对0xFF按位与运算,可以避免误差YYEncodingTypeUnknown =0,///< unknown 类型编码未知YYEncodingTypeVoid =1,///< voidYYEncodingTypeBool =2,///< boolYYEncodingTypeInt8 =3,///< char / BOOLYYEncodingTypeUInt8 =4,///< unsigned charYYEncodingTypeInt16 =5,///< shortYYEncodingTypeUInt16 =6,///< unsigned shortYYEncodingTypeInt32 =7,///< intYYEncodingTypeUInt32 =8,///< unsigned intYYEncodingTypeInt64 =9,///< long longYYEncodingTypeUInt64 =10,///< unsigned long longYYEncodingTypeFloat =11,///< floatYYEncodingTypeDouble =12,///< doubleYYEncodingTypeLongDouble =13,///< long doubleYYEncodingTypeObject =14,///< idYYEncodingTypeClass =15,///< ClassYYEncodingTypeSEL =16,///< SELYYEncodingTypeBlock =17,///< blockYYEncodingTypePointer =18,///< void*YYEncodingTypeStruct =19,///< structYYEncodingTypeUnion =20,///< unionYYEncodingTypeCString =21,///< char*YYEncodingTypeCArray =22,///< char[10] (for example)// Table 6-2 Qualifier限定符编码YYEncodingTypeQualifierMask =0xFF00,///< mask of qualifier 表示8 ~ 15位 的十六进制Mask掩码,用于得到枚举值的低15位值YYEncodingTypeQualifierConst =1<<8,///< constYYEncodingTypeQualifierIn =1<<9,///< inYYEncodingTypeQualifierInout =1<<10,///< inoutYYEncodingTypeQualifierOut =1<<11,///< outYYEncodingTypeQualifierBycopy =1<<12,///< bycopyYYEncodingTypeQualifierByref =1<<13,///< byrefYYEncodingTypeQualifierOneway =1<<14,///< oneway// Table 7-1 Property属性编码YYEncodingTypePropertyMask =0xFF0000,///< mask of property 表示16 ~ 24位 的十六进制Mask掩码,用于得到枚举值的低24位值YYEncodingTypePropertyReadonly =1<<16,///< readonlyYYEncodingTypePropertyCopy =1<<17,///< copyYYEncodingTypePropertyRetain =1<<18,///< retainYYEncodingTypePropertyNonatomic =1<<19,///< nonatomicYYEncodingTypePropertyWeak =1<<20,///< weakYYEncodingTypePropertyCustomGetter =1<<21,///< getter=YYEncodingTypePropertyCustomSetter =1<<22,///< setter=YYEncodingTypePropertyDynamic =1<<23,///< @dynamic};
枚举定义中,存在不同类型的定义(可以用0xFF、0xFF00、0xFF0000来区别),而不同的类型又需要组合。
Mask枚举值,没有实际作用,只是用来获取低n位的枚举数值,让枚举值之间可以使用运算符|、&
使用NS_OPTIONS来定义具有位移的枚举类型,NS_ENUM是通用情况
YYClassMethodInfo.h
YYModel采用Runtime直接调用 Getter/Setter,所以要将Runtime Method属性暴露出来提供MethodInfo类获取相应的属性
Runtime:Method information.
Method information.structobjc_method{SEL method_name OBJC2_UNAVAILABLE;// 方法名char*method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE;// 方法实现}
YYClassMethodInfo
@interfaceYYClassMethodInfo:NSObject@property(nonatomic,assign,readonly) Method method;///< method@property(nonatomic,strong,readonly)NSString*name;///< method name@property(nonatomic,assign,readonly) SEL sel;///< method's selector@property(nonatomic,assign,readonly) IMP imp;///< method's implementation@property(nonatomic,strong,readonly)NSString*typeEncoding;///< method's parameter and return types@property(nonatomic,strong,readonly)NSString*returnTypeEncoding;///< return value's type@property(nullable,nonatomic,strong,readonly)NSArray *argumentTypeEncodings;///< array of arguments' type- (instancetype)initWithMethod:(Method)method;@end
Runtime:Ivar information.
structobjc_ivar{char*ivar_name OBJC2_UNAVAILABLE;// 变量名char*ivar_type OBJC2_UNAVAILABLE;// 变量类型intivar_offset OBJC2_UNAVAILABLE;// 基地址偏移字节#ifdef__LP64__intspace OBJC2_UNAVAILABLE;#endif}
** YYClassIvarInfo**
@interfaceYYClassIvarInfo:NSObject@property(nonatomic,assign,readonly) Ivar ivar;///< ivar@property(nonatomic,strong,readonly)NSString*name;///< Ivar's name@property(nonatomic,assign,readonly) ptrdiff_t offset;///< Ivar's offset@property(nonatomic,strong,readonly)NSString*typeEncoding;///< Ivar's type encoding(?)@property(nonatomic,assign,readonly) YYEncodingType type;///< Ivar's type- (instancetype)initWithIvar:(Ivar)ivar;@end
Runtime:Class information.
structobjc_class{Class isa OBJC_ISA_AVAILABILITY;#if!__OBJC2__Class super_class OBJC2_UNAVAILABLE;// 父类constchar*name OBJC2_UNAVAILABLE;// 类名longversion OBJC2_UNAVAILABLE;// 类的版本信息,默认为0longinfo OBJC2_UNAVAILABLE;// 类信息,供运行期使用的一些位标识longinstance_size OBJC2_UNAVAILABLE;// 该类的实例变量大小structobjc_ivar_list*ivarsOBJC2_UNAVAILABLE;// 该类的成员变量链表structobjc_method_list**methodListsOBJC2_UNAVAILABLE;// 方法定义的链表structobjc_cache*cacheOBJC2_UNAVAILABLE;// 方法缓存structobjc_protocol_list*protocolsOBJC2_UNAVAILABLE;// 协议链表#endif} OBJC2_UNAVAILABLE;
YYClassInfo.m
@interfaceYYClassInfo:NSObject@property(nonatomic,assign,readonly) Class cls;///< class object@property(nullable,nonatomic,assign,readonly) Class superCls;///< super class object@property(nullable,nonatomic,assign,readonly) Class metaCls;///< class's meta class object@property(nonatomic,readonly)BOOLisMeta;///< whether this class is meta class@property(nonatomic,strong,readonly)NSString*name;///< class name@property(nullable,nonatomic,strong,readonly) YYClassInfo *superClassInfo;///< super class's class info@property(nullable,nonatomic,strong,readonly)NSDictionary *ivarInfos;///< ivars@property(nullable,nonatomic,strong,readonly)NSDictionary *methodInfos;///< methods@property(nullable,nonatomic,strong,readonly)NSDictionary *propertyInfos;///< properties....
YYEncodingGetType方法
通过指定类型编码字符串,返回类型编码字符串中Foundation Framework 编码字符和method encodings编码字符
// 通过指定类型编码字符串,返回类型编码字符串中Foundation Framework 编码字符和method encodings编码字符YYEncodingTypeYYEncodingGetType(constchar*typeEncoding){// 转换const限定符char*type = (char*)typeEncoding;// 传入字符串为NULL,返回未知类型if(!type)returnYYEncodingTypeUnknown;size_tlen =strlen(type);// 类型编码字符串的长度为空,返回未知类型编码if(len ==0)returnYYEncodingTypeUnknown;// @property (nonatomic(3:property), copy(2:property)) const(0:Qualifier) NSString(1:Foundation Framework ) *title(4); rT@"NSString",C,N,V_titleYYEncodingType qualifier =0;boolprefix =true;// 可能多个编码字符(多种方法修饰)while(prefix) {/** for type qualifiers(方法编码限定符,其中switch对应类型编码文档:)
Code Meaning
r const
n in
N inout
o out
O bycopy
R byref
V oneway
*/switch(*type) {case'r': { qualifier |= YYEncodingTypeQualifierConst; type++; }break;case'n': { qualifier |= YYEncodingTypeQualifierIn; type++; }break;case'N': { qualifier |= YYEncodingTypeQualifierInout; type++; }break;case'o': { qualifier |= YYEncodingTypeQualifierOut; type++; }break;case'O': { qualifier |= YYEncodingTypeQualifierBycopy; type++; }break;// bycopy 修饰的方法case'R': { qualifier |= YYEncodingTypeQualifierByref; type++; }break;// oneway 修饰的方法case'V': { qualifier |= YYEncodingTypeQualifierOneway; type++; }break;// 当前字符不再匹配 method encodings编码字符default: { prefix =false; }break; } }// 判断类型编码后续字符len =strlen(type);// 类型编码字符串的长度为空,返回字符串未知类型编码和method encodings限定符编码if(len ==0)returnYYEncodingTypeUnknown | qualifier;switch(*type) {/** For Foundation Framework
Code Meaning
c A char
i An int
s A short
l
A long l is treated as a 32-bit quantity on 64-bit programs.
q A long long
C An unsigned char
I An unsigned int
S An unsigned short
L An unsigned long
Q An unsigned long long
f A float
d A double
B A C++ bool or a C99 _Bool
v A void
* A character string (char *)
@ An object (whether statically typed or typed id)
# A class object (Class)
: A method selector (SEL)
[array type] An array
{name=type...} A structure
(name=type...) A union
bnum A bit field of num bits
^type A pointer to type
? An unknown type (among other things, this code is used for function pointers)
*/case'v':returnYYEncodingTypeVoid | qualifier;case'B':returnYYEncodingTypeBool | qualifier;case'c':returnYYEncodingTypeInt8 | qualifier;case'C':returnYYEncodingTypeUInt8 | qualifier;case's':returnYYEncodingTypeInt16 | qualifier;case'S':returnYYEncodingTypeUInt16 | qualifier;case'i':returnYYEncodingTypeInt32 | qualifier;case'I':returnYYEncodingTypeUInt32 | qualifier;case'l':returnYYEncodingTypeInt32 | qualifier;case'L':returnYYEncodingTypeUInt32 | qualifier;case'q':returnYYEncodingTypeInt64 | qualifier;case'Q':returnYYEncodingTypeUInt64 | qualifier;case'f':returnYYEncodingTypeFloat | qualifier;case'd':returnYYEncodingTypeDouble | qualifier;case'D':returnYYEncodingTypeLongDouble | qualifier;case'#':returnYYEncodingTypeClass | qualifier;case':':returnYYEncodingTypeSEL | qualifier;case'*':returnYYEncodingTypeCString | qualifier;case'^':returnYYEncodingTypePointer | qualifier;case'[':returnYYEncodingTypeCArray | qualifier;case'(':returnYYEncodingTypeUnion | qualifier;case'{':returnYYEncodingTypeStruct | qualifier;case'@': {if(len ==2&& *(type +1) =='?')returnYYEncodingTypeBlock | qualifier;elsereturnYYEncodingTypeObject | qualifier; }// 当前字符不再匹配 Foundation Framework 编码字符default:returnYYEncodingTypeUnknown | qualifier; }}
1.T@"NSDate",&,N,V_publishDate
2.T@?,C,N,V_ltBlock
3.T@,R,C,N,V_idReadonlyCopyNonatomic
'@' Meaning: An object (whether statically typed or typed id)如果是@那么,类型可能是对象或者是一个指向id,还有一种特殊情况,如果*(type + 1) (编码字符串下一位)是‘?’,或者当前所指向的编码字符长度为2,可能是类型可能是block(见2类型编码例子),
类型编码
实践一番类型编码看看编码具体格式,其中取出几个实例变量的类型编码讲解
LTBook.h
@interfaceLTBook:NSObject@property(copy,nonatomic)NSString*name;@property(nonatomic,assign)CGSizesize;@propertyNSString*desc;@property(nonatomic,strong)NSDictionary*likedUsers;//@property(nonatomic,strong)UIColor*color;@property(assign,nonatomic) uint64_t pages;@property(strong,nonatomic)NSDate*publishDate;@property(readonly,copy,nonatomic)ididReadonlyCopyNonatomic;@property(readonly,copy,nonatomic)NSStringconst*strName;@property(readonly,copy, atomic)NSString*strName2;@end
成员变量:pages 对应类型编码:propertiesTypeEncoding: TQ,N,V_pages
成员变量:publishDate 对应类型编码:propertiesTypeEncoding:T@"NSDate",&,N,V_publishDate
成员变量:publishDate 对应类型编码:propertiesTypeEncoding: T@"NSString",R,C,N,V_strName
第一个T是固定的
第一个属性T@“NSDate”,(指属性名所指向的类型为NSDate) 、T@(指属性名所指向的类型为id)、TQ(指属性名所指向的类型为uint64_t),更详细的可以查看 声明属性文档
Property Attribute Description Examples章节,
第三个属性表示声明的Property attribute,详细可以查看编码类型枚举(或者查看官方文档Table 7-1 Declared property type encodings)
如果有第四第五个属性,表示也是表示属性类型编码
最后一个属性V_是固定的紧接着pages、strName指的是属性名
从打印的结果或官网提供的属性文档,还可以获取到一些信息,1.assign、readwrite、atomic没有对应的属性类型编码,2.类型编码字符串的顺序,strong, readWrite, nonatomic等属性顺序是有一定规律的,在编写property的属性时应该注意编写的顺序,还可以时刻提醒自己类型编码字符串的顺序。
initWithIvar方法
该方法获得相应的Ivar属性,存入YYClassiVarInfo对应的属性,这里就不多说了
initWithMethod方法
该方法获得相应的Method属性,存入YYClassiVarInfo对应的属性
- (instancetype)initWithMethod:(Method)method {if(!method)returnnil;self= [superinit]; _method = method; _sel = method_getName(method); _imp = method_getImplementation(method);constchar*name = sel_getName(_sel);if(name) { _name = [NSStringstringWithUTF8String:name];// 在所有Runtime 以char *定义的API都被视为UTF-8编码,所以这里用stringWithUTF8String}constchar*typeEncoding = method_getTypeEncoding(method);if(typeEncoding) { _typeEncoding = [NSStringstringWithUTF8String:typeEncoding]; }char*returnType = method_copyReturnType(method);if(returnType) { _returnTypeEncoding = [NSStringstringWithUTF8String:returnType];// 该参数,暂无作用free(returnType); }unsignedintargumentCount = method_getNumberOfArguments(method);// 该参数,暂无作用if(argumentCount >0) {// 这一块暂时无意义NSMutableArray*argumentTypes = [NSMutableArraynew];for(unsignedinti =0; i < argumentCount; i++) {char*argumentType = method_copyArgumentType(method, i);NSString*type = argumentType ? [NSStringstringWithUTF8String:argumentType] :nil; [argumentTypes addObject:type ? type :@""];if(argumentType) free(argumentType); } _argumentTypeEncodings = argumentTypes; }returnself;}
initWithProperty方法
- (instancetype)initWithProperty:(objc_property_t)property {if(!property) {returnnil; }self= [selfinit]; _property = property;constchar*name = property_getName(property);if(name) { _name = [NSStringstringWithUTF8String:name]; } LTEncodingType type =0;unsignedintattrCount; objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);for(unsignedinti =0; i < attrCount; i++) {NSLog(@"attrs[i].name1[0]---%c",attrs[i].name[0]);switch(attrs[i].name[0]) {// 或者属性每一个类型编码,见声明属性文档:Declared property type encodings章节case'T': {// Type encoding T@"NSString",C,N,V_nameif(attrs[i].value) {// attrs[i].value : Foundation Framework Code; Example: attrs[i].name[0] = T,attrs->value = @? / @"NSString"/ @ / @"NSDate" / Q / @"UIColor"_typeEncoding = [NSStringstringWithUTF8String:attrs[i].value];// NSStringtype = LTEncodingGetType(attrs[i].value);// attrs[i].value = @"NSString" 此时传进去的@"NSString" 通过LTEncodingGetType方法获得对应 对应限定符 LTEncodingTypeObjectif((type & LTEncodingTypeMask) == LTEncodingTypeObject) { size_t len = strlen(attrs[i].value);// @"NSString" 长度则为11if(len >3) {// 一般属性的类型长度大于3charname[len -2];//原来是name[11] ,现在 11 - 2 name[9]name[len -3] ='\0';// name[9] = '\0' 设置最后索引为'\0' ,目的是去掉最有一个“memcpy(name, attrs[i].value +2, len -3);// 这行代码目的是获得属性名, 猜想:attrs[i].value + 2 = NSString",属性类型和属性名是共享一份内存的,这个方法copy一份内存到name,通过name就可以获得对应的属性定义的类型。_cls = objc_getClass(name);// name所指向的具体来说内存是属性定义的类型 这里通过name 获得int 、NSString} } } }break;// @property (nonatomic(3:property), copy(2:property)) const(0:Qualifier) NSString(1:Foundation Framework ) *title(4); rT@"NSString",C,N,V_title 获得 2 3 4 位置的编码类型case'V': {// Instance variableif(attrs[i].value) { _ivarName = [NSStringstringWithUTF8String:attrs[i].value]; } }break;case'R': { type |= LTEncodingTypePropertyReadonly; }break;case'C': { type |= LTEncodingTypePropertyCopy; }break;case'&': { type |= LTEncodingTypePropertyRetain; }break;case'N': { type |= LTEncodingTypePropertyNonatomic; }break;case'D': { type |= LTEncodingTypePropertyDynamic; }break;case'W': { type |= LTEncodingTypePropertyWeak; }break;case'G': {// For Example:@property(getter=intGetFoo, setter=intSetFoo:) int intSetterGetter; Save _getter/ _settertype |= LTEncodingTypePropertyCustomGetter;if(attrs[i].value) { _getter=NSSelectorFromString([NSStringstringWithUTF8String:attrs[i].value]); } }break;case'S': { type |= LTEncodingTypePropertyCustomSetter;if(attrs[i].value) { _setter=NSSelectorFromString([NSStringstringWithUTF8String:attrs[i].value]); } }break;default:break; } }if(attrs) { free(attrs); attrs =NULL; } _type = type;if(_name.length) {if(!_getter) { _getter=NSSelectorFromString(_name); }if(!_setter) {// if _setter not nil ,set Function name/**
For Example: name = _title 从t开始,而且t字母表示为大写(uppercaseString用字符串的大写字母标识)(substringToIndex取索引为1的字符,substringFromIndex:取1~~~~(length - 1) 字符)大写T
*/_setter=NSSelectorFromString([NSStringstringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]); } }returnself;}
initWithClass/classInfoWithClass方法
- (instancetype)initWithClass:(Class)cls {// 判断参数的合法性if(!cls)returnnil;self= [superinit]; _cls = cls; _superCls = class_getSuperclass(cls);// 获得父类_isMeta = class_isMetaClass(cls);// 是否有元组if(!_isMeta) { _metaCls = objc_getMetaClass(class_getName(cls));// 获得元组} _name =NSStringFromClass(cls);// // 获得类名[self_update];// 更新方法列表、属性列表、成员列表数据_superClassInfo = [self.class classInfoWithClass:_superCls];// 递归最上层父亲信息returnself;}+ (instancetype)classInfoWithClass:(Class)cls {// 判断传入值的合法性if(!cls)returnnil;staticCFMutableDictionaryRefclassCache;// 类缓存器staticCFMutableDictionaryRefmetaCache;// 元组缓存器staticdispatch_once_tonceToken;// 同步信号量staticdispatch_semaphore_t lock;dispatch_once(&onceToken, ^{// 保证该块代码在线程安全(内部有一个同步锁)只执行一次classCache =CFDictionaryCreateMutable(CFAllocatorGetDefault(),0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); metaCache =CFDictionaryCreateMutable(CFAllocatorGetDefault(),0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);/** dispatch_semaphore_create
创建新的计数信号量的初始值。
*/lock = dispatch_semaphore_create(1);});dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);LTClassInfo *info =CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridgeconstvoid*)(cls));// 判断该类是元组还是类(initWithClass方法中倒数第二行代码有调用classInfoWithClass方法传入的参数是元组,所以这里需要判断数据存入的是类换成年期还是元组缓存器),根据类名从缓存器中取出数据if(info && info->_needUpdate) { [info _update];}dispatch_semaphore_signal(lock);if(!info) {// 缓存器中没有该类信息,创建一个类信息info = [[LTClassInfo alloc] initWithClass:cls];if(info) {// 首先CFDictionaryRef是线程安全,这里加锁目的是为了保证内部数据的线程安全,所有访问CFDictionaryRef接口都要经过这个 lockdispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);// 设置信号量为1,相当于添加同步锁// 缓存classInfoCFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridgeconstvoid*)(cls), (__bridgeconstvoid*)(info));// 释放锁dispatch_semaphore_signal(lock); }}returninfo;}+ (instancetype)classInfoWithClass:(Class)cls {// 判断传入值的合法性if(!cls)returnnil;staticCFMutableDictionaryRefclassCache;// 类缓存器staticCFMutableDictionaryRefmetaCache;// 元组缓存器staticdispatch_once_tonceToken;// 同步信号量staticdispatch_semaphore_t lock;dispatch_once(&onceToken, ^{// 保证该块代码在线程安全(内部有一个同步锁)只执行一次classCache =CFDictionaryCreateMutable(CFAllocatorGetDefault(),0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); metaCache =CFDictionaryCreateMutable(CFAllocatorGetDefault(),0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);/** dispatch_semaphore_create
创建新的计数信号量的初始值。
*/lock = dispatch_semaphore_create(1); }); dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); LTClassInfo *info =CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridgeconstvoid*)(cls));// 判断该类是元组还是类(initWithClass方法中倒数第二行代码有调用classInfoWithClass方法传入的参数是元组,所以这里需要判断数据存入的是类换成年期还是元组缓存器),根据类名从缓存器中取出数据if(info && info->_needUpdate) { [info _update]; } dispatch_semaphore_signal(lock);if(!info) {// 缓存器中没有该类信息,创建一个类信息info = [[LTClassInfo alloc] initWithClass:cls];if(info) {// 首先CFDictionaryRef是线程安全,这里加锁目的是为了保证内部数据的线程安全,所有访问CFDictionaryRef接口都要经过这个 lockdispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);// 设置信号量为1,相当于添加同步锁// 缓存classInfoCFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridgeconstvoid*)(cls), (__bridgeconstvoid*)(info));// 释放锁dispatch_semaphore_signal(lock); } }returninfo;}
initWithClass方法将Class相关属性存入YYClassInfo并且内部调用classInfoWithClass方法,在classInfoWithClass方法内部定义了两个缓存器classCache、metaCache,来存储类缓存器、元组缓存器,如果该类存在父类信息,调用initWithClass方法,直至递归最上层父亲信息。
为什么使用CFMutableDictionaryRef作为数据存储,而不用NSDictionary?
(1)相对于 Foundation 的方法来说,CoreFoundation 的方法有更高的性能
(2)CFArrayApplyFunction() 和 CFDictionaryApplyFunction() 方法来遍历容器类能带来不少性能提升,但代码写起来会非常麻烦
为什么有元组缓存器?
Model JSON 转换过程中需要很多类的元数据,如果数据足够小,则全部缓存到内存中。
这里你肯定有疑问,保存元组的信息,它有什么作用?
根据苹果官方文档描述:objc_msgSend函数的调用过程,有以下描述
1.当调用实例方法时,它会首先在自身isa指针指向的类(class)methodLists中查找该方法,如果找不到则会通过class的super_class指针找到父类的类对象结构体,然后从methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级父类结构体中查找,直至根class;
2.当我们调用某个某个类方法时,它会首先通过自己的isa指针找到metaclass,并从其中methodLists中查找该类方法,如果找不到则会通过metaclass的super_class指针找到父类的metaclass对象结构体,然后从methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级父类结构体中查找,直至根metaclass;
3.实质这两个存储器作用便于objc_msgSend快速找到该信息,而且是用字典存储的,查找数据时间复杂度为O(1),效率高。
4.防止为了同一个NSObject对象Class解析多次
NSObject+YYModel
/// Foundation Class Type(Foundation框架类的类型)typedefNS_ENUM(NSUInteger, YYEncodingNSType) { YYEncodingTypeNSUnknown =0, YYEncodingTypeNSString, YYEncodingTypeNSMutableString, YYEncodingTypeNSValue, YYEncodingTypeNSNumber, YYEncodingTypeNSDecimalNumber, YYEncodingTypeNSData, YYEncodingTypeNSMutableData, YYEncodingTypeNSDate, YYEncodingTypeNSURL, YYEncodingTypeNSArray, YYEncodingTypeNSMutableArray, YYEncodingTypeNSDictionary, YYEncodingTypeNSMutableDictionary, YYEncodingTypeNSSet, YYEncodingTypeNSMutableSet,};/// Get the Foundation class type from property info.根据Class对象获得对应的Foundation类staticforce_inline YYEncodingNSType YYClassGetNSType(Class cls) {if(!cls)returnYYEncodingTypeNSUnknown;if([cls isSubclassOfClass:[NSMutableStringclass]])returnYYEncodingTypeNSMutableString;if([cls isSubclassOfClass:[NSStringclass]])returnYYEncodingTypeNSString;if([cls isSubclassOfClass:[NSDecimalNumberclass]])returnYYEncodingTypeNSDecimalNumber;if([cls isSubclassOfClass:[NSNumberclass]])returnYYEncodingTypeNSNumber;if([cls isSubclassOfClass:[NSValueclass]])returnYYEncodingTypeNSValue;if([cls isSubclassOfClass:[NSMutableDataclass]])returnYYEncodingTypeNSMutableData;if([cls isSubclassOfClass:[NSDataclass]])returnYYEncodingTypeNSData;if([cls isSubclassOfClass:[NSDateclass]])returnYYEncodingTypeNSDate;if([cls isSubclassOfClass:[NSURLclass]])returnYYEncodingTypeNSURL;if([cls isSubclassOfClass:[NSMutableArrayclass]])returnYYEncodingTypeNSMutableArray;if([cls isSubclassOfClass:[NSArrayclass]])returnYYEncodingTypeNSArray;if([cls isSubclassOfClass:[NSMutableDictionaryclass]])returnYYEncodingTypeNSMutableDictionary;if([cls isSubclassOfClass:[NSDictionaryclass]])returnYYEncodingTypeNSDictionary;if([cls isSubclassOfClass:[NSMutableSetclass]])returnYYEncodingTypeNSMutableSet;if([cls isSubclassOfClass:[NSSetclass]])returnYYEncodingTypeNSSet;returnYYEncodingTypeNSUnknown;}
** isKindOfClass、isMemberOfClass、isSubclassOfClass区别**
- (BOOL)isKindOfClass:(Class)aClass;
返回一个布尔值,该值指示class是一个给定的类的实例或者继承自该类的任何类的实例。
如果接收机是当前类实例或者类继承类的实例返回YES,否则返回NO
- (BOOL)isMemberOfClass:(Class)aClass;
返回一个布尔值,该值指示class是否是给定类的实例。
如果接收机是一类的一个实例返回YES,否则返回NO
+ (BOOL)isSubclassOfClass:(Class)aClass;
返回一个布尔值,该值指示class是否是该类的子类,或相同的一个给定的类。
** 为什么用内联函数来判断参数传入的类**
作者:尽量用纯 C 函数、内联函数,使用纯 C 函数可以避免 ObjC 的消息发送带来的开销,如果 C 函数比较小,使用 inline 可以避免一部分压栈弹栈等函数调用的开销。
/// Whether the type is c number.(判断类型编码是否C语言结构的类型)staticforce_inlineBOOLYYEncodingTypeIsCNumber(YYEncodingType type) {switch(type & YYEncodingTypeMask) {caseYYEncodingTypeBool:caseYYEncodingTypeInt8:caseYYEncodingTypeUInt8:caseYYEncodingTypeInt16:caseYYEncodingTypeUInt16:caseYYEncodingTypeInt32:caseYYEncodingTypeUInt32:caseYYEncodingTypeInt64:caseYYEncodingTypeUInt64:caseYYEncodingTypeFloat:caseYYEncodingTypeDouble:caseYYEncodingTypeLongDouble:returnYES;default:returnNO; }}
YYNSNumberCreateFromID方法
Paste_Image.png
YYEncodingTypeIsCNumber、YYNSNumberCreateFromID、YYNSDateFromString这几个方法作用都是类型转换
方法作用:有几种情况,1.传入的对象指向NSNumber类 2.传入的对象指向NSString类,最终的会转换返回NSNumber ,针对传入的对象指向NSString类的进行处理,其处理过程又分为两种情况,第一种情况是传入的值是true、false、yes、no、nil等格式1数据,另外一种是传入的值是5788.57格式2数据,具体处理详解见下面源码,当然还有可能传入的5788就不做处理直接返回了 。
为什么要进行处理?
(1)当 JSON/Dictionary 中的对象类型与 Model 属性不一致时,YYModel 将会进行如下自动转换。自动转换不支持的值将会被忽略,以避免各种潜在的崩溃问题。
(2)作者关于类型转换的描述是这样的,是之前在项目中遇到了这样一些情况:本来服务端给的参数是一个 number 类型,经过模型转换后,赋值到一个 NSNumber 的属性中去,但后来服务端不小心改掉了,换成了 string 类型,造成后续访问那个 NSNumber 属性时,实际访问的是 NSString ,然后造成了一些崩溃。开发时有些地方可能漏掉了类型检查,所以希望模型转换时,能更加自动一些,尽量避免各种崩溃问题。
三种格式
格式1、True、FALSE、YES、no、NULL、nil、Null、YES...
格式2、5788
格式3、5788.57
/// Parse a number value from 'id'. (1)id类型对象 -》 NSNumber对象 (2) id类型对象 -》NSString类型对象 -》NSNumber类型对象staticforce_inlineNSNumber*YYNSNumberCreateFromID(__unsafe_unretainedidvalue) {staticNSCharacterSet*dot;staticNSDictionary*dic;staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{// 传入的值属性格式1对其进行处理dot = [NSCharacterSetcharacterSetWithRange:NSMakeRange('.',1)];// KEY-Valuedic = @{@"TRUE": @(YES),@"True": @(YES),@"true": @(YES),@"FALSE": @(NO),@"False": @(NO),@"false": @(NO),@"YES": @(YES),@"Yes": @(YES),@"yes": @(YES),@"NO": @(NO),@"No": @(NO),@"no": @(NO),@"NIL": (id)kCFNull,@"Nil": (id)kCFNull,@"nil": (id)kCFNull,@"NULL": (id)kCFNull,@"Null": (id)kCFNull,@"null": (id)kCFNull,@"(NULL)": (id)kCFNull,@"(Null)": (id)kCFNull,@"(null)": (id)kCFNull,@"<NULL>": (id)kCFNull,@"<Null>": (id)kCFNull,@"<null>": (id)kCFNull}; });// 判断参数属于格式1,直接返回nilif(!value || value == (id)kCFNull)returnnil;// 判断参数类型是否标识数字类型NSNumber,属于格式2,无需处理直接返回值if([value isKindOfClass:[NSNumberclass]])returnvalue;// 判断参数是表示字符串NSStringif([value isKindOfClass:[NSStringclass]]) {NSNumber*num = dic[value];if(num) {if(num == (id)kCFNull)returnnil;returnnum; }// 如果传入的value值是5788.57, rangeOfCharacterFromSet查找字符串是否包含 `.`字符if([(NSString*)value rangeOfCharacterFromSet:dot].location !=NSNotFound) {constchar*cstring = ((NSString*)value).UTF8String;// 因为要调用atof方法所以将value的类型转换为UTF-8类型if(!cstring)returnnil;doublenum = atof(cstring);// double atof(const char *nptr); 将数字从NSString转化为doubleif(isnan(num) || isinf(num))returnnil;return@(num); }else{// value传入的值是5788转换为NSNumber数字类型constchar*cstring = ((NSString*)value).UTF8String;if(!cstring)returnnil;return@(atoll(cstring));// long long atoll(const char *nptr); 把字符串转换成长长整型数(64位)} }returnnil;}
为什么使用__unsafe_unretained?
作者回答:在 ARC 条件下,默认声明的对象是 __strong 类型的,赋值时有可能会产生 retain/release 调用,如果一个变量在其生命周期内不会被释放,则使用 __unsafe_unretained 会节省很大的开销。
网友提问: 楼主的偏好是说用__unsafe_unretained来代替__weak的使用,使用后自行解决野指针的问题吗?
作者回答:关于 __unsafe_unretained 这个属性,我只提到需要在性能优化时才需要尝试使用,平时开发自然是不推荐用的。
我的理解:貌似没有回答会不会自行解决野指针的问题,我的理解是使用__unsafe_unretained是否会出现野指针,根据使用环境进行分析,如果能保证所指的对象不为nil就不会出现野指针,例如在ModelSetValueForProperty传入value对其进行�if (value == (id)kCFNull) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);,如果value参数为空自然不会调用LTNSDateFromString方法,而且ModelSetValueForProperty这个方法定义的是static静态方法,变量在其生命周期内不会被释放,所以当方法执行完成后不会将该方法里的对象放入到autoreleasePool导致value被释放,在后续ModelSetValueForProperty方法中也没有对value = nil 操作,所以这里不会导致野指针的出现。
**YYNSDateFromString方法 **
/// Parse string to date.staticforce_inlineNSDate*YYNSDateFromString(__unsafe_unretainedNSString*string) {typedefNSDate* (^YYNSDateParseBlock)(NSString*string);#define kParserNum 34 // 日期字符串的最大长度,实际// 保存对应长度长度日期字符串解析的Block数组,设置数组内值都为0,使用静态来保存解析成NSDate的Block并且设置对应日期字符串长度staticYYNSDateParseBlock blocks[kParserNum +1] = {0};staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{ {/*
2014-01-20 // Google
*/NSDateFormatter*formatter = [[NSDateFormatteralloc] init]; formatter.locale = [[NSLocalealloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter.timeZone = [NSTimeZonetimeZoneForSecondsFromGMT:0]; formatter.dateFormat =@"yyyy-MM-dd";// 日期的字符串长度为10blocks[10] = ^(NSString*string) {return[formatter dateFromString:string]; }; } {/*
格式1:2014-01-20 12:24:48
格式2:2014-01-20T12:24:48 // Google
格式3:2014-01-20 12:24:48.000
格式4:2014-01-20T12:24:48.000
*//** 长度为19的日期字符串解析,分两种
*/NSDateFormatter*formatter1 = [[NSDateFormatteralloc] init]; formatter1.locale = [[NSLocalealloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter1.timeZone = [NSTimeZonetimeZoneForSecondsFromGMT:0]; formatter1.dateFormat =@"yyyy-MM-dd'T'HH:mm:ss";NSDateFormatter*formatter2 = [[NSDateFormatteralloc] init]; formatter2.locale = [[NSLocalealloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter2.timeZone = [NSTimeZonetimeZoneForSecondsFromGMT:0]; formatter2.dateFormat =@"yyyy-MM-dd HH:mm:ss";NSDateFormatter*formatter3 = [[NSDateFormatteralloc] init]; formatter3.locale = [[NSLocalealloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter3.timeZone = [NSTimeZonetimeZoneForSecondsFromGMT:0]; formatter3.dateFormat =@"yyyy-MM-dd'T'HH:mm:ss.SSS";NSDateFormatter*formatter4 = [[NSDateFormatteralloc] init]; formatter4.locale = [[NSLocalealloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter4.timeZone = [NSTimeZonetimeZoneForSecondsFromGMT:0]; formatter4.dateFormat =@"yyyy-MM-dd HH:mm:ss.SSS";// 将日期字符串解析的Block保存到19的数组位置blocks[19] = ^(NSString*string) {if([string characterAtIndex:10] =='T') {// 格式2return[formatter1 dateFromString:string]; }else{// 格式1return[formatter2 dateFromString:string]; } };// 将日期字符串解析的Block保存到23的数组位置blocks[23] = ^(NSString*string) {if([string characterAtIndex:10] =='T') {// 格式3return[formatter3 dateFromString:string]; }else{// 格式4return[formatter4 dateFromString:string]; } }; } {/*
格式1:2014-01-20T12:24:48Z // Github, Apple
格式2:2014-01-20T12:24:48+0800 // Facebook
格式3:2014-01-20T12:24:48+12:00 // Google
格式4:2014-01-20T12:24:48.000Z
格式5:2014-01-20T12:24:48.000+0800
格式6:2014-01-20T12:24:48.000+12:00
*/NSDateFormatter*formatter = [NSDateFormatternew]; formatter.locale = [[NSLocalealloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter.dateFormat =@"yyyy-MM-dd'T'HH:mm:ssZ";NSDateFormatter*formatter2 = [NSDateFormatternew]; formatter2.locale = [[NSLocalealloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter2.dateFormat =@"yyyy-MM-dd'T'HH:mm:ss.SSSZ";// 将日期字符串解析的Block保存到20的数组位置,格式1blocks[20] = ^(NSString*string) {return[formatter dateFromString:string]; };// 将日期字符串解析的Block保存到24的数组位置,格式2blocks[24] = ^(NSString*string) {return[formatter dateFromString:string]?: [formatter2 dateFromString:string]; };// 将日期字符串解析的Block保存到25的数组位置,格式3blocks[25] = ^(NSString*string) {return[formatter dateFromString:string]; };// 将日期字符串解析的Block保存到28的数组位置,格式4blocks[28] = ^(NSString*string) {return[formatter2 dateFromString:string]; };// 将日期字符串解析的Block保存到29的数组位置,格式5blocks[29] = ^(NSString*string) {return[formatter2 dateFromString:string]; }; } {/*
格斯1:Fri Sep 04 00:12:21 +0800 2015 // Weibo, Twitter
格式2:Fri Sep 04 00:12:21.000 +0800 2015
*/NSDateFormatter*formatter = [NSDateFormatternew]; formatter.locale = [[NSLocalealloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter.dateFormat =@"EEE MMM dd HH:mm:ss Z yyyy";NSDateFormatter*formatter2 = [NSDateFormatternew]; formatter2.locale = [[NSLocalealloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter2.dateFormat =@"EEE MMM dd HH:mm:ss.SSS Z yyyy";// 将日期字符串解析的Block保存到30的数组位置,格式1blocks[30] = ^(NSString*string) {return[formatter dateFromString:string]; };// 将日期字符串解析的Block保存到34的数组位置,格式2blocks[34] = ^(NSString*string) {return[formatter2 dateFromString:string]; }; } });// 判断传入值合法性if(!string)returnnil;// 如果传入字符串长度越界,不属于转换的范围内,直接返回nilif(string.length > kParserNum)returnnil;// 设置block数组大小YYNSDateParseBlock parser = blocks[string.length];if(!parser)returnnil;returnparser(string);#undef kParserNum}
**YYNSBlockClass方法 **
/// Get the 'NSBlock' class.获得NSBlock类staticforce_inline Class YYNSBlockClass() {staticClass cls;staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{void(^block)(void) = ^{}; cls = ((NSObject*)block).class;// 获得当前Block的Classwhile(class_getSuperclass(cls) != [NSObjectclass]) {// 遍历Block(__NSGlobalBlock__ - > __NSGlobalBlock - > NSBlock - > NSObject)最终的superClass,NSBlock的父类是NSObjectcls = class_getSuperclass(cls); } });returncls;// current is "NSBlock"}
**YYISODateFormatter **
/**
Get the ISO date formatter. 获得ISO日期格式
ISO:国际标准化组织的国际标准ISO 8601是日期和时间的表示方法
ISO8601 format example:
2010-07-09T16:13:30+12:00
2011-01-11T11:11:11+0000
2011-01-26T19:06:43Z
length: 20/24/25
*/staticforce_inlineNSDateFormatter*YYISODateFormatter() {staticNSDateFormatter*formatter =nil;staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{// 初始化formatterformatter = [[NSDateFormatteralloc] init];// 设置时区formatter.locale = [[NSLocalealloc] initWithLocaleIdentifier:@"en_US_POSIX"];// 设置ISO日期格式formatter.dateFormat =@"yyyy-MM-dd'T'HH:mm:ssZ"; });returnformatter;}
YYValueForKeyPath/YYValueForMultiKeys方法
/// Get the value with key paths from dictionary(从字典中获取关键路径的值)/// The dic should be NSDictionary, and the keyPath should not be nil. (dic[keyPaths[i]],应该是NSDictionaty字典,否则返回nil。)/**
json格式见下面方法
*/staticforce_inlineidLTValueForKeyPath(__unsafe_unretainedNSDictionary*dic, __unsafe_unretainedNSArray*keyPaths) {idvalue =nil;for(NSUIntegeri =0, max = keyPaths.count; i < max; i++) {/** 这里的keyPaths可以这么理解,如果传入的是以下JSON数据,那么keypath.count为2 此时max = 2
1. i = 0 , 0 = i < 2 = max; 2. url = keyPaths[0] = keyPaths[i]
3. http://example.com/1.png = value = dic[@"url"], 如果value不是NSDictionary类型直接返回
4. 0 + 1 = i + 1 < 2 = max
5. i++,
--------------------------
1. i = 1, 1 = i < 2 = max; 2. desc = keyPath[1] = keyPaths[i]
3. Happy~ = value = dic[@"desc"], 如果value不是NSDictionary类型直接返回
4. 1 + 1 < i + 1 < 2 = max 返回该值
*//**
格式2 "photos" : [ 格式2
{
"url":"http://example.com/1.png\",
"desc":"Happy~"
},
{
"url":"http://example.com/2.png\",
"desc":"Yeah!"
}
]
*/value = dic[keyPaths[i]];// 根据key取值,keypathif(i +1< max) {// 相当于NSArray数组倒数第二个key// 判断该值如果是字典,直接赋值,否则返回Nil,格式2内的格式6if([value isKindOfClass:[NSDictionaryclass]]) { dic = value;//}else{returnnil; } } }returnvalue;}/// Get the value with multi key (or key path) from dictionary(从字典中获取多个键(或KeyPath值相当于字典)的值)/// The dic should be NSDictionary(dic是一个字典)/** json
{
"name" : "Happy Birthday", 格式1
"photos" : [
{
"url":"http://example.com/1.png",
"desc":"Happy~"
},
{
"url":"http://example.com/2.png",
"desc":"Yeah!"
}
],
"likedUsers" : { 格式1
"Jony" : {"uid":10001,"name":"Jony"}, 格式3
"Anna" : {"uid":10002,"name":"Anna"}, 格式3
"desc":"Happy~", 格式4
格式2 "photos" : [ 格式6
{
"url":"http://example.com/1.png",
"desc":"Happy~"
},
{
"url":"http://example.com/2.png", 格式5
"desc":"Yeah!"
}
]
},
"likedUserIds" : [10001,10002]
}
*/staticforce_inlineidLTValueForMultiKeys(__unsafe_unretainedNSDictionary*dic, __unsafe_unretainedNSArray*multiKeys) {// dic 为格式1idvalue =nil;for(NSString*keyinmultiKeys) {// 遍历multiKeys数组,依次取出keyif([key isKindOfClass:[NSStringclass]]) {// 如果是key字符类型,根据key从dic直接取出数据,格式1内存在格式4value = dic[key];if(value)break;// 如果根据key取出对应的值,停止继续取值}else{// 根据Key的值如果NSSArray,进入LTValueForKeyPath取出指定key的值,例如格式1内存在格式2value = LTValueForKeyPath(dic, (NSArray*)key);// 如果根据key取出对应的值,停止继续取值if(value)break; } }returnvalue;}
_YYModelPropertyMeta
Paste_Image.png
Paste_Image.png
@interface_YYModelPropertyMeta:NSObject{@packageNSString*_name;///< property's name 属性名称// 对象类型YYEncodingType _type;///< property's type 属性的基础类型// 对象类型YYEncodingNSType _nsType;///< property's Foundation type 属性的 Foundation Class类型BOOL_isCNumber;///< is c number type 是否是C语言结构类型// 实例变量来源于哪个类(可能是父类) */Class _cls;///< property's class, or nilClass _genericCls;///< container's generic class, or nil if threr's no generic 容器的通用类,如果()没有通用class为nil,自定义类SEL _getter;///< getter, or nil if the instances cannot respond,保存属性的getter方法SEL _setter;///< setter, or nil if the instances cannot respond 保存属性的setter方法BOOL_isKVCCompatible;///< YES if it can access with key-value coding 类型是否不支持KVCBOOL_isStructAvailableForKeyedArchiver;///< YES if the struct can encoded with keyed archiver/unarchiver 结构是否支持 archiver(归档)/unarchiver(解档) BOOL_hasCustomClassFromDictionary;///< class/generic class implements +modelCustomClassForDictionary: 字典转模型是否类被实现/*
property->key: _mappedToKey:key _mappedToKeyPath:nil _mappedToKeyArray:nil
property->keyPath: _mappedToKey:keyPath _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil
property->keys: _mappedToKey:keys[0] _mappedToKeyPath:nil/keyPath _mappedToKeyArray:keys(array)
*//**
*/NSString*_mappedToKey;///< the key mapped to to key// 如果有多级映射 如果有多个属性映射到相同的键会用到NSArray*_mappedToKeyPath;///< the key path mapped to (nil if the name is not key path)NSArray*_mappedToKeyArray;///< the key(NSString) or keyPath(NSArray) array (nil if not mapped to multiple keys)YYClassPropertyInfo *_info;///< property's info_YYModelPropertyMeta *_next;///< next meta if there are multiple properties mapped to the same key. 下一个元,如果有多个属性映射到相同的键。}@end
_YYModelPropertyMeta属声明属性详细解析
_name: 属性的名称
_type: 属性对应的基础类型编码
_nsType: Foundation框架类型编码
_isCNumber:判断是否C语言结构类型
_cls:实例变量来源于哪个类
_genericCls:如下例子,genericCls = LMBook,该值用来判断是否自定义映射,如果是自定义映射则值为自定义映射的当前类
@impleentationLMBook+ (NSDictionary *)modelCustomPropertyMapper {return@{@"name" : @"n", @"page": @"p", @"desc": @"ext.desc", @"bookID": @[@"id", @"ID", @"book_id"]};}@end
_getter:保存属性的getter方法
_setter:保存属性的setter方法
_isKVCCompatible:判断是否支持KVC
_isStructAvailableForKeyedArchiver:判断结构是否支持 archiver(归档)/unarchiver(解档) 编码
_hasCustomClassFromDictionary:判断是否存在自定义映射的字典
_mappedToKey:简单映射Key->Value,例如以下情况
@{@"name" : @"n", @"page": @"p"}
_mappedToKeyPath:稍微复杂映射,例如以下情况格式1
json: {"n":"Harry Pottery","p": 256, 格式1"ext": {"desc":"A book written by J.K.Rowling."},"ID": 100010 }
_mappedToKeyArray:实例属性Key映射多个不同的Json key,例如格式1情况
@{@"name" : @"n", @"page": @"p", @"desc": @"ext.desc", 格式1@"bookID": @[@"id", @"ID", @"book_id"]
metaWithClassInfo方法
// 根据YYClassInfo信息初始化YYClassInfo并且设置对应属性信息+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic{ _YYModelPropertyMeta *meta = [selfnew]; meta->_name = propertyInfo.name; meta->_type = propertyInfo.type; meta->_info = propertyInfo; meta->_genericCls = generic;// 如果属性的类型编码是对象,例如id, NSDate...,如果是对象类型那么一般是Foundation类型,直接根据属性的属性的类信息传入当前属性class信息或者对应Foundation类型if((meta->_type & YYEncodingTypeMask) == YYEncodingTypeObject) {// 属性的 Foundation Class类型meta->_nsType = YYClassGetNSType(propertyInfo.cls); }else{// 属性是c语言基础类型meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_type); }if((meta->_type & YYEncodingTypeMask) == YYEncodingTypeStruct) {// 属性是结构体类型/* It seems that NSKeyedUnarchiver cannot decode NSValue except these structs: iOS能够归档的struct类型有限制,能够使用归档的struct类型type encodings为如下: 32 bit struct类型的@encode() */staticNSSet *types = nil;staticdispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSMutableSet *set = [NSMutableSetnew];// 32 bit[set addObject:@"{CGSize=ff}"]; [set addObject:@"{CGPoint=ff}"]; [set addObject:@"{CGRect={CGPoint=ff}{CGSize=ff}}"]; [set addObject:@"{CGAffineTransform=ffffff}"]; [set addObject:@"{UIEdgeInsets=ffff}"]; [set addObject:@"{UIOffset=ff}"];/** iOS能够归档的struct类型有限制,能够使用归档的struct类型type encodings为如下: 64 bit struct类型的@encode() */// 64 bit[set addObject:@"{CGSize=dd}"]; [set addObject:@"{CGPoint=dd}"]; [set addObject:@"{CGRect={CGPoint=dd}{CGSize=dd}}"]; [set addObject:@"{CGAffineTransform=dddddd}"]; [set addObject:@"{UIEdgeInsets=dddd}"]; [set addObject:@"{UIOffset=dd}"]; types = set; });if([types containsObject:propertyInfo.typeEncoding]) { meta->_isStructAvailableForKeyedArchiver = YES; } } meta->_cls = propertyInfo.cls;if(generic) {// 如果存在自定义映射meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)];// 从传入的generic class读取自定义映射,设置该代理方法为第一响应}elseif(meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) {// 属性类名不为空且不是Foundation类型meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)];// 从属性变量类型的Class读取自定义映射,并且设置该代理方法为第一响应}// 保存Property的getterif(propertyInfo.getter) {/**
instancesRespondToSelector:被调用时,动态方法是有机会的首先为selector提供一个IMP,如果该类对应的Property有getter方法实现方法,则返回YES
*/if([classInfo.cls instancesRespondToSelector:propertyInfo.getter]) { meta->_getter = propertyInfo.getter;// 从属性列表获取响应属性的getter方法} }// 保存Property的setterif(propertyInfo.setter) {/**
instancesRespondToSelector:被调用时,动态方法是有机会的首先为selector提供一个IMP,如果该类对应的Property有setter方法实现方法,则返回YES
*/if([classInfo.cls instancesRespondToSelector:propertyInfo.setter]) { meta->_setter = propertyInfo.setter; } }/** 属性变量是否支持KVC,有一个条件
* getter/setter方法必须实现
*/if(meta->_getter && meta->_setter) {/*
KVC invalid type:
long double
pointer (such as SEL/CoreFoundation object)
*//** 有两种类型不支持KVC
* 1.long double 不支持KVC
* 2. pointer (such as SEL/CoreFoundation object) 不支持KVC
*/switch(meta->_type & YYEncodingTypeMask) {caseYYEncodingTypeBool:caseYYEncodingTypeInt8:caseYYEncodingTypeUInt8:caseYYEncodingTypeInt16:caseYYEncodingTypeUInt16:caseYYEncodingTypeInt32:caseYYEncodingTypeUInt32:caseYYEncodingTypeInt64:caseYYEncodingTypeUInt64:caseYYEncodingTypeFloat:caseYYEncodingTypeDouble:caseYYEncodingTypeObject:caseYYEncodingTypeClass:caseYYEncodingTypeBlock:caseYYEncodingTypeStruct:caseYYEncodingTypeUnion: { meta->_isKVCCompatible = YES; }break;default:break; } }returnmeta;}
第一遍未修改版本,未完待续