轉: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閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容