YYModel源码详细解析-1

作者: js丶 | 来源:发表于2016-06-18 01:59 被阅读2996次

    前言:

    阅读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.

    typedef NS_OPTIONS(NSUInteger, YYEncodingType) {
        
    typedef NS_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, ///< void
        YYEncodingTypeBool       = 2, ///< bool
        YYEncodingTypeInt8       = 3, ///< char / BOOL
        YYEncodingTypeUInt8      = 4, ///< unsigned char
        YYEncodingTypeInt16      = 5, ///< short
        YYEncodingTypeUInt16     = 6, ///< unsigned short
        YYEncodingTypeInt32      = 7, ///< int
        YYEncodingTypeUInt32     = 8, ///< unsigned int
        YYEncodingTypeInt64      = 9, ///< long long
        YYEncodingTypeUInt64     = 10, ///< unsigned long long
        YYEncodingTypeFloat      = 11, ///< float
        YYEncodingTypeDouble     = 12, ///< double
        YYEncodingTypeLongDouble = 13, ///< long double
        YYEncodingTypeObject     = 14, ///< id
        YYEncodingTypeClass      = 15, ///< Class
        YYEncodingTypeSEL        = 16, ///< SEL
        YYEncodingTypeBlock      = 17, ///< block
        YYEncodingTypePointer    = 18, ///< void*
        YYEncodingTypeStruct     = 19, ///< struct
        YYEncodingTypeUnion      = 20, ///< union
        YYEncodingTypeCString    = 21, ///< char*
        YYEncodingTypeCArray     = 22, ///< char[10] (for example)
        
        
        // Table 6-2 Qualifier限定符编码
        YYEncodingTypeQualifierMask   = 0xFF00,   ///< mask of qualifier 表示8 ~ 15位 的十六进制Mask掩码,用于得到枚举值的低15位值
        YYEncodingTypeQualifierConst  = 1 << 8,  ///< const
        YYEncodingTypeQualifierIn     = 1 << 9,  ///< in
        YYEncodingTypeQualifierInout  = 1 << 10, ///< inout
        YYEncodingTypeQualifierOut    = 1 << 11, ///< out
        YYEncodingTypeQualifierBycopy = 1 << 12, ///< bycopy
        YYEncodingTypeQualifierByref  = 1 << 13, ///< byref
        YYEncodingTypeQualifierOneway = 1 << 14, ///< oneway
        
        
        // Table 7-1 Property属性编码
        YYEncodingTypePropertyMask         = 0xFF0000, ///< mask of property 表示16 ~ 24位 的十六进制Mask掩码,用于得到枚举值的低24位值
        YYEncodingTypePropertyReadonly     = 1 << 16, ///< readonly
        YYEncodingTypePropertyCopy         = 1 << 17, ///< copy
        YYEncodingTypePropertyRetain       = 1 << 18, ///< retain
        YYEncodingTypePropertyNonatomic    = 1 << 19, ///< nonatomic
        YYEncodingTypePropertyWeak         = 1 << 20, ///< weak
        YYEncodingTypePropertyCustomGetter = 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.
     
    struct objc_method {
    
        SEL method_name                 OBJC2_UNAVAILABLE;  // 方法名
    
        char *method_types                  OBJC2_UNAVAILABLE;
    
        IMP method_imp                      OBJC2_UNAVAILABLE;  // 方法实现
    
    }  
    

    YYClassMethodInfo

    @interface YYClassMethodInfo : 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<NSString *> *argumentTypeEncodings; ///< array of arguments' type
    - (instancetype)initWithMethod:(Method)method;
    @end
    

    Runtime:Ivar information.

    struct objc_ivar {
        char *ivar_name                 OBJC2_UNAVAILABLE;  // 变量名
        char *ivar_type                 OBJC2_UNAVAILABLE;  // 变量类型
        int ivar_offset                 OBJC2_UNAVAILABLE;  // 基地址偏移字节
    #ifdef __LP64__
        int space                       OBJC2_UNAVAILABLE;
    #endif
    
    } 
    

    ** YYClassIvarInfo**

    @interface YYClassIvarInfo : 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.

    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    #if !__OBJC2__
    
        Class super_class                       OBJC2_UNAVAILABLE;  // 父类
    
        const char *name                        OBJC2_UNAVAILABLE;  // 类名
    
        long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
    
        long info                               OBJC2_UNAVAILABLE;  // 类信息,供运行期使用的一些位标识
    
        long instance_size                      OBJC2_UNAVAILABLE;  // 该类的实例变量大小
    
        struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 该类的成员变量链表
    
        struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定义的链表
    
        struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法缓存
    
        struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议链表
    
    #endif
    } OBJC2_UNAVAILABLE;
    

    YYClassInfo.m

    @interface YYClassInfo : 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) BOOL isMeta; ///< 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<NSString *, YYClassIvarInfo *> *ivarInfos; ///< ivars
    @property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; ///< methods
    @property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; ///< properties
    ....
    

    YYEncodingGetType方法
    通过指定类型编码字符串,返回类型编码字符串中Foundation Framework 编码字符和method encodings编码字符

    // 通过指定类型编码字符串,返回类型编码字符串中Foundation Framework 编码字符和method encodings编码字符
    YYEncodingType YYEncodingGetType(const char *typeEncoding) {
        // 转换const限定符
        char *type = (char *)typeEncoding;
        // 传入字符串为NULL,返回未知类型
        if (!type) return YYEncodingTypeUnknown;
        size_t len = strlen(type);
        // 类型编码字符串的长度为空,返回未知类型编码
        if (len == 0) return YYEncodingTypeUnknown;
        // @property (nonatomic(3:property), copy(2:property)) const(0:Qualifier) NSString(1:Foundation Framework ) *title(4);   rT@"NSString",C,N,V_title
        YYEncodingType qualifier = 0;
        bool prefix = 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) return YYEncodingTypeUnknown | 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': return YYEncodingTypeVoid | qualifier;
            case 'B': return YYEncodingTypeBool | qualifier;
            case 'c': return YYEncodingTypeInt8 | qualifier;
            case 'C': return YYEncodingTypeUInt8 | qualifier;
            case 's': return YYEncodingTypeInt16 | qualifier;
            case 'S': return YYEncodingTypeUInt16 | qualifier;
            case 'i': return YYEncodingTypeInt32 | qualifier;
            case 'I': return YYEncodingTypeUInt32 | qualifier;
            case 'l': return YYEncodingTypeInt32 | qualifier;
            case 'L': return YYEncodingTypeUInt32 | qualifier;
            case 'q': return YYEncodingTypeInt64 | qualifier;
            case 'Q': return YYEncodingTypeUInt64 | qualifier;
            case 'f': return YYEncodingTypeFloat | qualifier;
            case 'd': return YYEncodingTypeDouble | qualifier;
            case 'D': return YYEncodingTypeLongDouble | qualifier;
            case '#': return YYEncodingTypeClass | qualifier;
            case ':': return YYEncodingTypeSEL | qualifier;
            case '*': return YYEncodingTypeCString | qualifier;
            case '^': return YYEncodingTypePointer | qualifier;
            case '[': return YYEncodingTypeCArray | qualifier;
            case '(': return YYEncodingTypeUnion | qualifier;
            case '{': return YYEncodingTypeStruct | qualifier;
            case '@': {
                if (len == 2 && *(type + 1) == '?')
                    return YYEncodingTypeBlock | qualifier;
                else
                    return YYEncodingTypeObject | qualifier;
            }
            // 当前字符不再匹配 Foundation Framework 编码字符
            default: return YYEncodingTypeUnknown | 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

    @interface LTBook : NSObject
    
    @property (copy, nonatomic) NSString *name;
    @property (nonatomic, assign) CGSize size;
    @property NSString *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) id idReadonlyCopyNonatomic;
    @property (readonly, copy, nonatomic)  NSString const *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) return nil;
        self = [super init];
        _method = method;
        _sel = method_getName(method);
        _imp = method_getImplementation(method);
        const char *name = sel_getName(_sel);
        if (name) {
            _name = [NSString stringWithUTF8String:name]; // 在所有Runtime 以char *定义的API都被视为UTF-8编码,所以这里用stringWithUTF8String
        }
        const char *typeEncoding = method_getTypeEncoding(method);
        if (typeEncoding) {
            _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
        }
        char *returnType = method_copyReturnType(method);
        if (returnType) {
            _returnTypeEncoding = [NSString stringWithUTF8String:returnType]; // 该参数,暂无作用
            free(returnType);
        }
        unsigned int argumentCount = method_getNumberOfArguments(method); // 该参数,暂无作用
        if (argumentCount > 0) {  // 这一块暂时无意义
            NSMutableArray *argumentTypes = [NSMutableArray new];
            for (unsigned int i = 0; i < argumentCount; i++) {
                char *argumentType = method_copyArgumentType(method, i);
                NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil;
                [argumentTypes addObject:type ? type : @""];
                if (argumentType) free(argumentType);
            }
            _argumentTypeEncodings = argumentTypes;
        }
        return self;
    }
    

    initWithProperty方法

    - (instancetype)initWithProperty:(objc_property_t)property {
        if (!property) {
            return nil;
        }
        self = [self init];
        _property = property;
        const char *name = property_getName(property);
        if (name) {
            _name = [NSString stringWithUTF8String:name];
        }
        
        LTEncodingType type = 0;
        unsigned int attrCount;
        objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
        
        for (unsigned int i = 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_name
                    if (attrs[i].value) {  // attrs[i].value : Foundation Framework  Code; Example: attrs[i].name[0] = T,attrs->value = @? / @"NSString"/ @ / @"NSDate" / Q / @"UIColor"
                        _typeEncoding = [NSString stringWithUTF8String:attrs[i].value]; // NSString
                        
                        type = LTEncodingGetType(attrs[i].value); // attrs[i].value =  @"NSString" 此时传进去的@"NSString" 通过LTEncodingGetType方法获得对应  对应限定符 LTEncodingTypeObject
                        if ((type & LTEncodingTypeMask) == LTEncodingTypeObject) {
                            size_t len = strlen(attrs[i].value); // @"NSString" 长度则为11
                            if (len > 3) { // 一般属性的类型长度大于3
                                char name[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 variable
                    if (attrs[i].value) {
                        _ivarName = [NSString stringWithUTF8String: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/ _setter
                    type |= LTEncodingTypePropertyCustomGetter;
                    if (attrs[i].value) {
                        
                        _getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
                    }
                } break;
                case 'S': {
                    type |= LTEncodingTypePropertyCustomSetter;
                    if (attrs[i].value) {
                        _setter = NSSelectorFromString([NSString stringWithUTF8String: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([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
            }
        }
        return self;
    }
    

    initWithClass/classInfoWithClass方法

    - (instancetype)initWithClass:(Class)cls {
        // 判断参数的合法性
        if (!cls) return nil;
        self = [super init];
        _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]; // 递归最上层父亲信息
        return self;
    }
    
    
    + (instancetype)classInfoWithClass:(Class)cls {
    // 判断传入值的合法性
    if (!cls) return nil;
    static CFMutableDictionaryRef classCache; // 类缓存器
    static CFMutableDictionaryRef metaCache; // 元组缓存器
    static dispatch_once_t onceToken;
    // 同步信号量
    static dispatch_semaphore_t lock;
    dispatch_once(&onceToken, ^{  // 保证该块代码在线程安全(内部有一个同步锁)只执行一次
        classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        /** dispatch_semaphore_create
         创建新的计数信号量的初始值。
         */
        lock = dispatch_semaphore_create(1);
    });
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    LTClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls)); // 判断该类是元组还是类(initWithClass方法中倒数第二行代码有调用classInfoWithClass方法传入的参数是元组,所以这里需要判断数据存入的是类换成年期还是元组缓存器),根据类名从缓存器中取出数据
    if (info && info->_needUpdate) {
        [info _update];
    }
    dispatch_semaphore_signal(lock);
    if (!info) { // 缓存器中没有该类信息,创建一个类信息
        info = [[LTClassInfo alloc] initWithClass:cls];
        if (info) {
            // 首先CFDictionaryRef是线程安全,这里加锁目的是为了保证内部数据的线程安全,所有访问CFDictionaryRef接口都要经过这个 lock
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); // 设置信号量为1,相当于添加同步锁
            // 缓存classInfo
            CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
            
            // 释放锁
            dispatch_semaphore_signal(lock);
        }
    }
    return info;
    }
    
    + (instancetype)classInfoWithClass:(Class)cls {
        // 判断传入值的合法性
        if (!cls) return nil;
        static CFMutableDictionaryRef classCache; // 类缓存器
        static CFMutableDictionaryRef metaCache; // 元组缓存器
        static dispatch_once_t onceToken;
        // 同步信号量
        static dispatch_semaphore_t lock;
        dispatch_once(&onceToken, ^{  // 保证该块代码在线程安全(内部有一个同步锁)只执行一次
            classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
            metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
            /** dispatch_semaphore_create
             创建新的计数信号量的初始值。
             */
            lock = dispatch_semaphore_create(1);
        });
        dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
        LTClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls)); // 判断该类是元组还是类(initWithClass方法中倒数第二行代码有调用classInfoWithClass方法传入的参数是元组,所以这里需要判断数据存入的是类换成年期还是元组缓存器),根据类名从缓存器中取出数据
        if (info && info->_needUpdate) {
            [info _update];
        }
        dispatch_semaphore_signal(lock);
        if (!info) { // 缓存器中没有该类信息,创建一个类信息
            info = [[LTClassInfo alloc] initWithClass:cls];
            if (info) {
                // 首先CFDictionaryRef是线程安全,这里加锁目的是为了保证内部数据的线程安全,所有访问CFDictionaryRef接口都要经过这个 lock
                dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); // 设置信号量为1,相当于添加同步锁
                // 缓存classInfo
                CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
                
                // 释放锁
                dispatch_semaphore_signal(lock);
            }
        }
        return info;
    }
    
    • 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框架类的类型)
    typedef NS_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类
    static force_inline YYEncodingNSType YYClassGetNSType(Class cls) {
        if (!cls) return YYEncodingTypeNSUnknown;  
        if ([cls isSubclassOfClass:[NSMutableString class]]) return YYEncodingTypeNSMutableString;
        if ([cls isSubclassOfClass:[NSString class]]) return YYEncodingTypeNSString;
        if ([cls isSubclassOfClass:[NSDecimalNumber class]]) return YYEncodingTypeNSDecimalNumber;
        if ([cls isSubclassOfClass:[NSNumber class]]) return YYEncodingTypeNSNumber;
        if ([cls isSubclassOfClass:[NSValue class]]) return YYEncodingTypeNSValue;
        if ([cls isSubclassOfClass:[NSMutableData class]]) return YYEncodingTypeNSMutableData;
        if ([cls isSubclassOfClass:[NSData class]]) return YYEncodingTypeNSData;
        if ([cls isSubclassOfClass:[NSDate class]]) return YYEncodingTypeNSDate;
        if ([cls isSubclassOfClass:[NSURL class]]) return YYEncodingTypeNSURL;
        if ([cls isSubclassOfClass:[NSMutableArray class]]) return YYEncodingTypeNSMutableArray;
        if ([cls isSubclassOfClass:[NSArray class]]) return YYEncodingTypeNSArray;
        if ([cls isSubclassOfClass:[NSMutableDictionary class]]) return YYEncodingTypeNSMutableDictionary;
        if ([cls isSubclassOfClass:[NSDictionary class]]) return YYEncodingTypeNSDictionary;
        if ([cls isSubclassOfClass:[NSMutableSet class]]) return YYEncodingTypeNSMutableSet;
        if ([cls isSubclassOfClass:[NSSet class]]) return YYEncodingTypeNSSet;
        return YYEncodingTypeNSUnknown;
    }
    

    ** 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语言结构的类型)
    static force_inline BOOL YYEncodingTypeIsCNumber(YYEncodingType type) {
        switch (type & YYEncodingTypeMask) {
            case YYEncodingTypeBool:
            case YYEncodingTypeInt8:
            case YYEncodingTypeUInt8:
            case YYEncodingTypeInt16:
            case YYEncodingTypeUInt16:
            case YYEncodingTypeInt32:
            case YYEncodingTypeUInt32:
            case YYEncodingTypeInt64:
            case YYEncodingTypeUInt64:
            case YYEncodingTypeFloat:
            case YYEncodingTypeDouble:
            case YYEncodingTypeLongDouble: return YES;
            default: return NO;
        }
    }
    

    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类型对象
    static force_inline NSNumber *YYNSNumberCreateFromID(__unsafe_unretained id value) {
        static NSCharacterSet *dot;
        static NSDictionary *dic;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{ // 传入的值属性格式1对其进行处理
            dot = [NSCharacterSet characterSetWithRange:NSMakeRange('.', 1)];
            // KEY-Value
            dic = @{@"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,直接返回nil
        if (!value || value == (id)kCFNull) return nil;
        // 判断参数类型是否标识数字类型NSNumber,属于格式2,无需处理直接返回值
        if ([value isKindOfClass:[NSNumber class]]) return value;
        // 判断参数是表示字符串NSString
        if ([value isKindOfClass:[NSString class]]) {
            NSNumber *num = dic[value];
            if (num) {
                if (num == (id)kCFNull) return nil;
                return num;
            }
            // 如果传入的value值是5788.57,  rangeOfCharacterFromSet查找字符串是否包含 `.`字符
            if ([(NSString *)value rangeOfCharacterFromSet:dot].location != NSNotFound) {
                const char *cstring = ((NSString *)value).UTF8String; // 因为要调用atof方法所以将value的类型转换为UTF-8类型
                if (!cstring) return nil;
                double num = atof(cstring); // double atof(const char *nptr); 将数字从NSString转化为double
                if (isnan(num) || isinf(num)) return nil;
                return @(num);
            } else { // value传入的值是5788转换为NSNumber数字类型
                const char *cstring = ((NSString *)value).UTF8String;
                if (!cstring) return nil;
                return @(atoll(cstring));  //  long long atoll(const char *nptr); 把字符串转换成长长整型数(64位)
            }
        }
        
        return nil;
    }
    
    • 为什么使用__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.
    static force_inline NSDate *YYNSDateFromString(__unsafe_unretained NSString *string) {
        typedef NSDate* (^YYNSDateParseBlock)(NSString *string);
        #define kParserNum 34 // 日期字符串的最大长度,实际
        // 保存对应长度长度日期字符串解析的Block数组,设置数组内值都为0,使用静态来保存解析成NSDate的Block并且设置对应日期字符串长度
        static YYNSDateParseBlock blocks[kParserNum + 1] = {0};
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            {
                /*
                 2014-01-20  // Google
                 */
                NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
                formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
                formatter.dateFormat = @"yyyy-MM-dd";
                // 日期的字符串长度为10
                blocks[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 = [[NSDateFormatter alloc] init];
                formatter1.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter1.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
                formatter1.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss";
                
                NSDateFormatter *formatter2 = [[NSDateFormatter alloc] init];
                formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter2.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
                formatter2.dateFormat = @"yyyy-MM-dd HH:mm:ss";
    
                NSDateFormatter *formatter3 = [[NSDateFormatter alloc] init];
                formatter3.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter3.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
                formatter3.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS";
    
                NSDateFormatter *formatter4 = [[NSDateFormatter alloc] init];
                formatter4.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter4.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
                formatter4.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS";
                // 将日期字符串解析的Block保存到19的数组位置
                blocks[19] = ^(NSString *string) {
                    if ([string characterAtIndex:10] == 'T') { // 格式2
                        return [formatter1 dateFromString:string];
                    } else { // 格式1
                        return [formatter2 dateFromString:string];
                    }
                };
                // 将日期字符串解析的Block保存到23的数组位置
                blocks[23] = ^(NSString *string) {
                    if ([string characterAtIndex:10] == 'T') { // 格式3
                        return [formatter3 dateFromString:string];
                    } else { // 格式4
                        return [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 = [NSDateFormatter new];
                formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
    
                NSDateFormatter *formatter2 = [NSDateFormatter new];
                formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter2.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZ";
                // 将日期字符串解析的Block保存到20的数组位置,格式1
                blocks[20] = ^(NSString *string) { return [formatter dateFromString:string]; };
                // 将日期字符串解析的Block保存到24的数组位置,格式2
                blocks[24] = ^(NSString *string) { return [formatter dateFromString:string]?: [formatter2 dateFromString:string]; };
                // 将日期字符串解析的Block保存到25的数组位置,格式3
                blocks[25] = ^(NSString *string) { return [formatter dateFromString:string]; };
                // 将日期字符串解析的Block保存到28的数组位置,格式4
                blocks[28] = ^(NSString *string) { return [formatter2 dateFromString:string]; };
                // 将日期字符串解析的Block保存到29的数组位置,格式5
                blocks[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 = [NSDateFormatter new];
                formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy";
    
                NSDateFormatter *formatter2 = [NSDateFormatter new];
                formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
                formatter2.dateFormat = @"EEE MMM dd HH:mm:ss.SSS Z yyyy";
                // 将日期字符串解析的Block保存到30的数组位置,格式1
                blocks[30] = ^(NSString *string) { return [formatter dateFromString:string]; };
                // 将日期字符串解析的Block保存到34的数组位置,格式2
                blocks[34] = ^(NSString *string) { return [formatter2 dateFromString:string]; };
            }
        });
        // 判断传入值合法性
        if (!string) return nil;
        // 如果传入字符串长度越界,不属于转换的范围内,直接返回nil
        if (string.length > kParserNum) return nil;
        // 设置block数组大小
        YYNSDateParseBlock parser = blocks[string.length];
        if (!parser) return nil;
        return parser(string);
        #undef kParserNum
    }
    

    **YYNSBlockClass方法 **

    /// Get the 'NSBlock' class.获得NSBlock类
    static force_inline Class YYNSBlockClass() {
        static Class cls;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            void (^block)(void) = ^{};
            cls = ((NSObject *)block).class; // 获得当前Block的Class
            while (class_getSuperclass(cls) != [NSObject class]) { // 遍历Block(__NSGlobalBlock__ - > __NSGlobalBlock - > NSBlock - > NSObject)最终的superClass,NSBlock的父类是NSObject
                
                cls = class_getSuperclass(cls);
            }
        });
        return cls; // 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
     */
     static force_inline NSDateFormatter *YYISODateFormatter() {
        static NSDateFormatter *formatter = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            // 初始化formatter
            formatter = [[NSDateFormatter alloc] init];
            // 设置时区
            formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
            // 设置ISO日期格式
            formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
        });
        return formatter;
    }
    

    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格式见下面方法
     */
    
    
    static force_inline id LTValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) {
        id value = nil;
        for (NSUInteger i = 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取值,keypath
            if (i + 1 < max) { // 相当于NSArray数组倒数第二个key
                // 判断该值如果是字典,直接赋值,否则返回Nil,格式2内的格式6
                if ([value isKindOfClass:[NSDictionary class]]) {
                    dic = value; //
                } else {
                    return nil;
                }
            }
        }
        return value;
    }
    
    /// 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]
     }
     
     */
    static force_inline id LTValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) {// dic 为格式1
        id value = nil;
        for (NSString *key in multiKeys) { // 遍历multiKeys数组,依次取出key
            if ([key isKindOfClass:[NSString class]]) { // 如果是key字符类型,根据key从dic直接取出数据,格式1内存在格式4
                value = dic[key];
                if (value) break; // 如果根据key取出对应的值,停止继续取值
            } else { // 根据Key的值如果NSSArray,进入LTValueForKeyPath取出指定key的值,例如格式1内存在格式2
                value = LTValueForKeyPath(dic, (NSArray *)key);
                // 如果根据key取出对应的值,停止继续取值
                if (value) break;
            }
        }
        return value;
    }
    

    _YYModelPropertyMeta

    Paste_Image.png Paste_Image.png
    @interface _YYModelPropertyMeta : NSObject {
        @package
        NSString *_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 nil
        Class _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 类型是否不支持KVC
        BOOL _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,该值用来判断是否自定义映射,如果是自定义映射则值为自定义映射的当前类
    @impleentation LMBook
    + (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 = [self new];
        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()
             */
            static NSSet *types = nil;
            static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                NSMutableSet *set = [NSMutableSet new];
                // 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读取自定义映射,设置该代理方法为第一响应
        } else if (meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) { // 属性类名不为空且不是Foundation类型
            meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)];  // 从属性变量类型的Class读取自定义映射,并且设置该代理方法为第一响应
        }
        // 保存Property的getter
        if (propertyInfo.getter) {
            /**
            instancesRespondToSelector:被调用时,动态方法是有机会的首先为selector提供一个IMP,如果该类对应的Property有getter方法实现方法,则返回YES
             */
            if ([classInfo.cls instancesRespondToSelector:propertyInfo.getter]) {
                meta->_getter = propertyInfo.getter; // 从属性列表获取响应属性的getter方法
            }
        }
        // 保存Property的setter
        if (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) {
                case YYEncodingTypeBool:
                case YYEncodingTypeInt8:
                case YYEncodingTypeUInt8:
                case YYEncodingTypeInt16:
                case YYEncodingTypeUInt16:
                case YYEncodingTypeInt32:
                case YYEncodingTypeUInt32:
                case YYEncodingTypeInt64:
                case YYEncodingTypeUInt64:
                case YYEncodingTypeFloat:
                case YYEncodingTypeDouble:
                case YYEncodingTypeObject:
                case YYEncodingTypeClass:
                case YYEncodingTypeBlock:
                case YYEncodingTypeStruct:
                case YYEncodingTypeUnion: {
                    meta->_isKVCCompatible = YES;
                } break;
                default: break;
            }
        }
        
        return meta;
    }
    

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

    相关文章

      网友评论

      • NearMilk:为什么+ (instancetype)classInfoWithClass:(Class)cls 写了两次,而且没有_update方法的介绍
      • 做一个有爱的伸手党:写的很好 现在只能看得懂一点 等我在用一段时间再过来看看 应该能有很多提升
      • MoussyL:谢谢作者分享~图太赞了
        有个疑问,刚开始看源码,发现大部分用Core Foundation框架里的数据类型,例如CFMutableDictionaryRef等,想问下这只是个人的编码习惯,还是CF框架有自己的优势,看到很多地方都要__bridge来桥接对象,感觉挺麻烦的,所以很纳闷为什么很多第三方框架喜欢这么写?希望作者可以解答一下,万分感谢~:pray::+1:
        MoussyL:@js丶 万分感谢,这个问题困扰了我好久,作者给的答案很详细,原理很清晰对我帮助很大,是我知识面太窄了,会参考作者给的链接好好学习一下,再次感谢:kissing_heart:
        js丶:@木子夕 为什么大部分用Core Foundation框架里的数据类型,例如CFMutableDictionaryRef等
        1.相对于 Foundation 的方法来说,CoreFoundation 的方法有更高的性能
        2.同时YYModel源码中你还可以看到使用了CFArrayApplyFunction、CFDictionaryApplyFunction两个方法,当解析字段过多、嵌套复杂时,这两个方法配套使用能带来不少性能提升,这也是为什么很多第三方框架喜欢这么写,具体性能提升可以查阅(http://ridiculousfish.com/blog/posts/array.html)得知,只是代码写起来比较麻烦.
        3.更进一步其实从自我提升来说的话CoreFoundation框架优势就是开源,实现细节可以从(https://github.com/apple/swift-corelibs-foundation/blob/173e9ea16cf1a0ed9b21cc11ea8a2d15b3f8dca9/CoreFoundation/Collections.subproj)看到,从中得知CFMutableArray是由hash table或binary tree数据结构构建的,CFMutableDictionary:是由hash table或binary tree、bit_vector位向量容器来构建的,当你想构建一个存储结构时,这些都是非常有用的资料
      • zl520k:分析的不错的,我想问一下,这个源码,你有没有看到值得优化的地方?
      • 5a3db39f0bd6:最近正在学习YYModel,正打算也写个详解,一搜。。看到哥你写的瞬间石化。。写得太详细了,这图画的666,求分享画图软件
        js丶:@玛丽_ 请问啥意思呢
        MMD_:大兄弟你这转得太快了 原谅我放荡不羁的笑
        js丶:@DUALgu 在线工具画图:https://www.processon.com/,建议以后可以用Sketch来画,视觉效果更好

      本文标题:YYModel源码详细解析-1

      本文链接:https://www.haomeiwen.com/subject/xtvmdttx.html