转:YYModel源码详细解析-1

YYModel源码详细解析-1

 

js丶 关注

2016.06.18 01:59* 字数 3167 阅读 3800评论 12喜欢 37

前言:

阅读YYModel之前建议先阅读Runtime基础篇YYModel采用Runtime直接调用 Getter/Setter,是一款高性能 iOS/OSX 模型转换框架,支持定义映射过程。正好最近想深入Runtime一番,于是就读了几遍YYModel的源码,本文将从作者提供的性能优化的几个 Tip,还有结合Github 上的 Issues,以及相关项目线索为起点对YYModel源码进行分析,并且绘制一张项目的整体架构图,设计的思想。

源码来源:

YYModel

资料参考文献

Type Encodings

Declared Properties

Runtime参考文档

项目整体架构:

项目图

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;}

第一遍未修改版本,未完待续

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容