YYModel源代碼分析(二)YYClassInfo

前言

本文的中文注釋代碼demo更新在我的github上。

上篇 YYModel源代碼分析(一)整體介紹
主要寫了YYModel的整體結構,代碼調用思路以及頭文件YYModel.h代碼。本篇會主要集中在YYClassInfo文件上。文章內容會包含一些與JSONModel的比較,想了解JSONModel,可以參考JSONModel源代碼解析。

主體分層

YYClassInfo主要分為以下幾部分:

  • typedef NS_OPTIONS(NSUInteger, YYEncodingType)YYEncodingType YYEncodingGetType(const char *typeEncoding);方法
  • @interface YYClassIvarInfo : NSObject
  • @interface YYClassMethodInfo : NSObject
  • @interface YYClassPropertyInfo : NSObject
  • @interface YYClassInfo : NSObject

以下將分別分析每一部分的源代碼。

YYClassInfo源代碼

(1).typedef NS_OPTIONS(NSUInteger, YYEncodingType)與YYEncodingType YYEncodingGetType(const char *typeEncoding)方法

相關知識

在這邊,對于YYEncodingType的了解,需要知道一個很重要的概念:

Type Encodings

To assist the runtime system, the compiler encodes the return and argument types for each method in a character string and associates the string with the method selector. The coding scheme it uses is also useful in other contexts and so is made publicly available with the@encode()
compiler directive. When given a type specification, @encode()
returns a string encoding that type. The type can be a basic type such as an int
, a pointer, a tagged structure or union, or a class name—any type, in fact, that can be used as an argument to the Csizeof()
operator.

看過我JSONModel解析的人,應該比較了解這一塊,property attribute的解析就是通過type encode解析出來的string進行的解析。

但在這里的type encoding相對于JSONModel中的使用,會更general一些:

  • JSONModel是只針對于Class的property變量,所以在解析的時候,將Ivar默認包含在property中,通過property的property attribute一并解析出來。
  • YYModel中,包含了Class的property變量,還加上了Class的Method方法與Ivar的實例變量。對于Ivar來說,可能還存在在方法參數中,所以說所需要解析的類型會更加多一些。

代碼

在YYClassInfo.h中,先定義了一個NS_OPTIONS:

typedef NS_OPTIONS(NSUInteger, YYEncodingType) {
    //0~8位:變量類型
    YYEncodingTypeMask       = 0xFF, ///< mask of type value
    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)
    
    //8~16位:方法類型
    YYEncodingTypeQualifierMask   = 0xFF00,   ///< mask of qualifier
    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
    
    //16~24位:property修飾類型
    YYEncodingTypePropertyMask         = 0xFF0000, ///< mask of property
    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
};

該NS_OPTIONS主要定義了3個大類encode type:

  • YYEncodingTypeMask:
    變量類型,因為類型只會有一種,所以就用數字站位
  • YYEncodingTypeQualifierMask:
    方法中的參數變量修飾符,理論上只有解析Method的參數才能解析到
  • YYEncodingTypePropertyMask
    property修飾符類型

這邊對于YYEncodingTypeQualifierMask和YYEncodingTypePropertyMask因為存在多種可能的情況,使用了位移(<<)的方式,通過與(&)YYEncodingTypeQualifierMask和YYEncodingTypePropertyMask的方式,判斷是否包含某個值。

獲取Ivar類型的函數如下:

//解析Ivar的type encode string
YYEncodingType YYEncodingGetType(const char *typeEncoding) {
    char *type = (char *)typeEncoding;
    if (!type) return YYEncodingTypeUnknown;
    size_t len = strlen(type);
    if (len == 0) return YYEncodingTypeUnknown;
    
    YYEncodingType qualifier = 0;
    bool prefix = true;
    while (prefix) {
        //方法參數Ivar中的解析,理論上解析不到該類參數
        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;
            case 'R': {
                qualifier |= YYEncodingTypeQualifierByref;
                type++;
            } break;
            case 'V': {
                qualifier |= YYEncodingTypeQualifierOneway;
                type++;
            } break;
            default: { prefix = false; } break;
        }
    }

    len = strlen(type);
    if (len == 0) return YYEncodingTypeUnknown | qualifier;
    
    //返回值類型解析
    switch (*type) {
        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;     //OC Block
            else
                return YYEncodingTypeObject | qualifier;    //OC對象
        }
        default: return YYEncodingTypeUnknown | qualifier;
    }
}

該函數也是通過獲得的type encode的string,對照著表進行解析,因為是解析Ivar,所以也只包含了YYEncodingTypeMask和YYEncodingTypeQualifierMask。而YYEncodingTypePropertyMask會包含在property的解析中。

(2).@interface YYClassIvarInfo : NSObject

相關知識

我下載了runtime的源代碼objc4-680.tar.gz,以下代碼基于該版本。該版本包含舊代碼與新代碼,以下全部基于新代碼。

Ivar:An opaque type that represents an instance variable(實例變量,跟某個對象關聯,不能被靜態方法使用,與之想對應的是class variable).

typedef struct ivar_t *Ivar;

struct ivar_t {
#if __x86_64__
    // *offset was originally 64-bit on some x86_64 platforms.
    // We read and write only 32 bits of it.
    // Some metadata provides all 64 bits. This is harmless for unsigned 
    // little-endian values.
    // Some code uses all 64 bits. class_addIvar() over-allocates the 
    // offset for their benefit.
#endif
    int32_t *offset;
    const char *name;
    const char *type;
    // alignment is sometimes -1; use alignment() instead
    uint32_t alignment_raw;
    uint32_t size;

    uint32_t alignment() const {
        if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
        return 1 << alignment_raw;
    }
};

代碼

YYClassIvarInfo類聲明:

/**
 Instance variable information.
 */
@interface YYClassIvarInfo : NSObject
@property (nonatomic, assign, readonly) Ivar ivar;              ///< ivar opaque struct ivar本身指針
@property (nonatomic, strong, readonly) NSString *name;         ///< Ivar's name        ivar名
@property (nonatomic, assign, readonly) ptrdiff_t offset;       ///< Ivar's offset      ivar偏移量
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< Ivar's type encoding   ivar encode string
@property (nonatomic, assign, readonly) YYEncodingType type;    ///< Ivar's type        ivar encode解析值

/**
 Creates and returns an ivar info object.
 
 @param ivar ivar opaque struct
 @return A new object, or nil if an error occurs.
 */
- (instancetype)initWithIvar:(Ivar)ivar;
@end

initWithIvar方法實現:

- (instancetype)initWithIvar:(Ivar)ivar {
    if (!ivar) return nil;
    self = [super init];
    _ivar = ivar;
    const char *name = ivar_getName(ivar);      //獲取ivar名
    if (name) {
        _name = [NSString stringWithUTF8String:name];
    }
    _offset = ivar_getOffset(ivar);             //獲取便宜量
    const char *typeEncoding = ivar_getTypeEncoding(ivar);  //獲取類型encode string
    if (typeEncoding) {
        _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
        _type = YYEncodingGetType(typeEncoding);    //類型解析
    }
    return self;
}

YYClassIvarInfo本身就是對系統Ivar的一層封裝,并進行了一次類型的解析。

實例

用YYModel測試用例來進行觀察:
YYTestNestRepo實現:

@interface YYTestNestRepo : NSObject
@property uint64_t repoID;
@property NSString *name;
@property YYTestNestUser *user;
@end
@implementation YYTestNestRepo
@end

YYTestNestRepo調用:

NSString *json = @"{\"repoID\":1234,\"name\":\"YYModel\",\"user\":{\"uid\":5678,\"name\":\"ibireme\"}}";
YYTestNestRepo *repo = [YYTestNestRepo yy_modelWithJSON:json];

設置解析斷點在解析@property YYTestNestUser *user;的Ivar變量處:

YYClassIvarInfo-user

(3).@interface YYClassMethodInfo : NSObject

相關知識

Method:An opaque type that represents a method in a class definition.

typedef struct method_t *Method;

struct method_t {
    SEL name;
    const char *types;
    IMP imp;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

其中包含兩個結構體SEL和IMP:

SEL:An opaque type that represents a method selector

Method selectors are used to represent the name of a method at runtime. A method selector is a C string that has been registered (or “mapped“) with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded.

typedef struct objc_selector *SEL;

IMP:A pointer to the function of a method implementation

This data type is a pointer to the start of the function that implements the method. This function uses standard C calling conventions as implemented for the current CPU architecture. The first argument is a pointer to self (that is, the memory for the particular instance of this class, or, for a class method, a pointer to the metaclass). The second argument is the method selector. The method arguments follow.

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

代碼

YYClassMethodInfo類聲明:

@interface YYClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) Method method;                  ///< method opaque struct method指針
@property (nonatomic, strong, readonly) NSString *name;                 ///< method name            method名
@property (nonatomic, assign, readonly) SEL sel;                        ///< method's selector      method selector
@property (nonatomic, assign, readonly) IMP imp;                        ///< method's implementation    method implementation
@property (nonatomic, strong, readonly) NSString *typeEncoding;         ///< method's parameter and return types    method的參數和返回類型
@property (nonatomic, strong, readonly) NSString *returnTypeEncoding;   ///< return value's type    method返回值的encode types
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *argumentTypeEncodings; ///< array of arguments' type method參數列表

- (instancetype)initWithMethod:(Method)method;
@end

initWithMethod方法實現:

- (instancetype)initWithMethod:(Method)method {
    if (!method) return nil;
    self = [super init];
    _method = method;
    _sel = method_getName(method);                      //獲取方法名,在oc中,方法名就是selector的標志
    _imp = method_getImplementation(method);            //獲取方法實現
    const char *name = sel_getName(_sel);
    if (name) {
        _name = [NSString stringWithUTF8String:name];
    }
    const char *typeEncoding = method_getTypeEncoding(method);  //獲得方法參數和返回值
    if (typeEncoding) {
        _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
    }
    char *returnType = method_copyReturnType(method);           //獲得返回值encode string
    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);        //獲得該參數的encode string
            NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil;
            [argumentTypes addObject:type ? type : @""];
            if (argumentType) free(argumentType);
        }
        _argumentTypeEncodings = argumentTypes;
    }
    return self;
}

實例

用YYModel測試用例來進行觀察:
YYTestNestRepo實現:

@interface YYTestNestRepo : NSObject
@property uint64_t repoID;
@property NSString *name;
@property YYTestNestUser *user;
@end
@implementation YYTestNestRepo
@end

YYTestNestRepo調用:

NSString *json = @"{\"repoID\":1234,\"name\":\"YYModel\",\"user\":{\"uid\":5678,\"name\":\"ibireme\"}}";
YYTestNestRepo *repo = [YYTestNestRepo yy_modelWithJSON:json];

設置解析斷點解析user方法:

YYClassMethodInfo-user

對于property來說,本質是:Ivar+getter+setter,所以設置了property也會觸發initWithMethod解析-(YYTestNestUser *) user;方法,該方法的解析如上圖。

這邊比較有意思的是,明明user沒有參數,怎么method_getNumberOfArguments解析出來2個參數
原因就是方法調用最后都會轉成((void (*)(id, SEL))objc_msgSend)((id)m, @selector(user));,所以會有兩個參數。

(4).@interface YYClassPropertyInfo : NSObject

相關知識

Property:An opaque type that represents an Objective-C declared property.

typedef struct property_t *objc_property_t;

struct property_t {
    const char *name;
    const char *attributes;
};

其中對于attributes就是property屬性的encode string。具體解析可以參考JSONModel的文章。

代碼

YYClassPropertyInfo類聲明:

@interface YYClassPropertyInfo : NSObject
@property (nonatomic, assign, readonly) objc_property_t property; ///< property's opaque struct     property指針
@property (nonatomic, strong, readonly) NSString *name;           ///< property's name              property名
@property (nonatomic, assign, readonly) YYEncodingType type;      ///< property's type              property encode解析值
@property (nonatomic, strong, readonly) NSString *typeEncoding;   ///< property's encoding value    property encode string
@property (nonatomic, strong, readonly) NSString *ivarName;       ///< property's ivar name         property對應的ivar名字
@property (nullable, nonatomic, assign, readonly) Class cls;      ///< may be nil                   property如果是oc類型,oc類型對應的class
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *protocols; ///< may nil      property如果存在protocol,protocol列表
@property (nonatomic, assign, readonly) SEL getter;               ///< getter (nonnull)             property的getter方法
@property (nonatomic, assign, readonly) SEL setter;               ///< setter (nonnull)             property的setter方法

- (instancetype)initWithProperty:(objc_property_t)property;
@end

initWithProperty方法實現:

- (instancetype)initWithProperty:(objc_property_t)property {
    if (!property) return nil;
    self = [super init];
    _property = property;
    const char *name = property_getName(property);              //獲得property名
    if (name) {
        _name = [NSString stringWithUTF8String:name];
    }
    
    YYEncodingType type = 0;
    unsigned int attrCount;
    objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);    //獲得所有property的attribute array
    for (unsigned int i = 0; i < attrCount; i++) {
        switch (attrs[i].name[0]) {
            case 'T': { // Type encoding            表示是property類型
                if (attrs[i].value) {
                    _typeEncoding = [NSString stringWithUTF8String:attrs[i].value];     //獲得attribute的encode string
                    type = YYEncodingGetType(attrs[i].value);                           //解析type
                    
                    if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) {      //代表是OC類型
                        NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];               //掃描attribute的encode string
                        if (![scanner scanString:@"@\"" intoString:NULL]) continue;                //不包含@\"代表不是oc類型,跳過
                        
                        NSString *clsName = nil;
                        if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {  //掃描oc類型string,在 \"之前
                            if (clsName.length) _cls = objc_getClass(clsName.UTF8String);               //獲得oc對象類型,并附值
                        }
                        
                        NSMutableArray *protocols = nil;
                        while ([scanner scanString:@"<" intoString:NULL]) {                 //掃描<>中的protocol類型,并設置
                            NSString* protocol = nil;
                            if ([scanner scanUpToString:@">" intoString: &protocol]) {
                                if (protocol.length) {
                                    if (!protocols) protocols = [NSMutableArray new];
                                    [protocols addObject:protocol];
                                }
                            }
                            [scanner scanString:@">" intoString:NULL];
                        }
                        _protocols = protocols;
                    }
                }
            } break;
            case 'V': { // Instance variable                        //ivar變量
                if (attrs[i].value) {
                    _ivarName = [NSString stringWithUTF8String:attrs[i].value];
                }
            } break;
            case 'R': {                                             //以下為property的幾種類型掃描,setter和getter方法要記錄方法名
                type |= YYEncodingTypePropertyReadonly;
            } break;
            case 'C': {
                type |= YYEncodingTypePropertyCopy;
            } break;
            case '&': {
                type |= YYEncodingTypePropertyRetain;
            } break;
            case 'N': {
                type |= YYEncodingTypePropertyNonatomic;
            } break;
            case 'D': {
                type |= YYEncodingTypePropertyDynamic;
            } break;
            case 'W': {
                type |= YYEncodingTypePropertyWeak;
            } break;
            case 'G': {
                type |= YYEncodingTypePropertyCustomGetter;
                if (attrs[i].value) {
                    _getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
                }
            } break;
            case 'S': {
                type |= YYEncodingTypePropertyCustomSetter;
                if (attrs[i].value) {
                    _setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
                }
            } // break; commented for code coverage in next line
            default: break;
        }
    }
    if (attrs) {                //有attrs要free
        free(attrs);
        attrs = NULL;
    }
    
    _type = type;               //最后設置encode解析值
    if (_name.length) {             //設置默認的getter方法和setter方法
        if (!_getter) {
            _getter = NSSelectorFromString(_name);
        }
        if (!_setter) {
            _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
        }
    }
    return self;
}

這段的解析方式和之前JSONModel的解析property方式有些類似,也不多做介紹了。

實例

用YYModel測試用例來進行觀察:
YYTestNestRepo實現:

@interface YYTestNestRepo : NSObject
@property uint64_t repoID;
@property NSString *name;
@property YYTestNestUser *user;
@end
@implementation YYTestNestRepo
@end

YYTestNestRepo調用:

NSString *json = @"{\"repoID\":1234,\"name\":\"YYModel\",\"user\":{\"uid\":5678,\"name\":\"ibireme\"}}";
YYTestNestRepo *repo = [YYTestNestRepo yy_modelWithJSON:json];

設置解析斷點解析property-user:

property-user

(5).@interface YYClassInfo : NSObject

相關知識

Class:An opaque type that represents an Objective-C class.

typedef struct objc_class *Class;

struct objc_class : objc_object {
    Class superclass;
    const char *name;
    uint32_t version;
    uint32_t info;
    uint32_t instance_size;
    struct old_ivar_list *ivars;
    struct old_method_list **methodLists;
    Cache cache;
    struct old_protocol_list *protocols;
    // CLS_EXT only
    const uint8_t *ivar_layout;
    struct old_class_ext *ext;

    ...
}

struct objc_object {
private:
    isa_t isa;

public:
 
    ...
}

對Class的superclass和isa指針來說,網上有一個特別多轉載的圖:


superclass and metaclass

上圖實線是 super_class 指針,虛線是isa指針。 有趣的是根元類的超類是NSObject,而isa指向了自己,而NSObject的超類為nil,也就是它沒有超類。

代碼

YYClassInfo類聲明:

@interface YYClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls; ///< class object                        class指針
@property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class object   superClass指針
@property (nullable, nonatomic, assign, readonly) Class metaCls;  ///< class's meta class object    metaClass指針
@property (nonatomic, readonly) BOOL isMeta; ///< whether this class is meta class          是否該class是metaclass
@property (nonatomic, strong, readonly) NSString *name; ///< class name                     class名
@property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< super class's class info    superClass的classinfo(緩存)
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; ///< ivars        ivar的dictionary,key為ivar的name
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; ///< methods  method的dictionary,key為method的name
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; ///< properties   properties的dictionary,key為property的name
//設置class更新,比如動態增加了一個方法,需要更新class
- (void)setNeedUpdate;
//返回class是否需要更新,更新則應該調用下面兩個方法之一
- (BOOL)needUpdate;
+ (nullable instancetype)classInfoWithClass:(Class)cls;
+ (nullable instancetype)classInfoWithClassName:(NSString *)className;

@end

YYClassInfo中有一個needUpdate是否更新的標識符,當手動更改class結構(比如class_addMethod()等)的時候,可以調用方法:

@implementation YYClassInfo {
    BOOL _needUpdate;           //是否需要更新private變量
}

- (void)setNeedUpdate {         //設置需要更新
    _needUpdate = YES;
}

- (BOOL)needUpdate {            //返回是否需要更新
    return _needUpdate;
}

實際的解析方法:


//多一層class從nsstring到class對象的轉換
+ (instancetype)classInfoWithClassName:(NSString *)className {
    Class cls = NSClassFromString(className);
    return [self classInfoWithClass:cls];
}

//class解析主體方法
+ (instancetype)classInfoWithClass:(Class)cls {
    if (!cls) return nil;
    static CFMutableDictionaryRef classCache;           //class緩存
    static CFMutableDictionaryRef metaCache;            //meta class緩存
    static dispatch_once_t onceToken;
    static dispatch_semaphore_t lock;                   //鎖
    dispatch_once(&onceToken, ^{                        //初始化兩種緩存
        classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        lock = dispatch_semaphore_create(1);
    });
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);       //只允許同時1個線程
    YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));        //獲取曾經解析過的緩存
    if (info && info->_needUpdate) {        //如果存在且需要更新,則重新解析class并更新結構體
        [info _update];
    }
    dispatch_semaphore_signal(lock);                        //釋放鎖
    if (!info) {                                            //如果沒有緩存,則第一次解析class
        info = [[YYClassInfo alloc] initWithClass:cls];
        if (info) {                                         //解析完畢設置緩存
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
            dispatch_semaphore_signal(lock);
        }
    }
    return info;
}

classInfoWithClass方法中主要調用了兩個方法- (instancetype)initWithClass:(Class)cls(初始化class)和- (void)_update(更新class),接下來看該兩個方法的實現。

//初始化class對象方法
- (instancetype)initWithClass:(Class)cls {
    if (!cls) return nil;
    self = [super init];
    _cls = cls;
    _superCls = class_getSuperclass(cls);           //設置superclass
    _isMeta = class_isMetaClass(cls);               //判斷是否是metaclass
    if (!_isMeta) {                                 //不是的話獲得meta class
        _metaCls = objc_getMetaClass(class_getName(cls));
    }
    _name = NSStringFromClass(cls);                 //獲得類名
    [self _update];                                 //進行更新

    _superClassInfo = [self.class classInfoWithClass:_superCls];    //遞歸superclass
    return self;
}

這邊也用到了````- (void)_update``(更新class),這應該就是class的核心更新方法:

//更新函數
- (void)_update {
    _ivarInfos = nil;                   //重置ivar,mthod,property3個緩存dictionary
    _methodInfos = nil;
    _propertyInfos = nil;
    
    Class cls = self.cls;
    unsigned int methodCount = 0;
    Method *methods = class_copyMethodList(cls, &methodCount);
    if (methods) {                      //解析method,并以name為key,進行緩存設置
        NSMutableDictionary *methodInfos = [NSMutableDictionary new];
        _methodInfos = methodInfos;
        for (unsigned int i = 0; i < methodCount; i++) {
            YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]];
            if (info.name) methodInfos[info.name] = info;
        }
        free(methods);
    }
    unsigned int propertyCount = 0;
    objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
    if (properties) {                   //解析property,并以name為key,進行緩存設置
        NSMutableDictionary *propertyInfos = [NSMutableDictionary new];
        _propertyInfos = propertyInfos;
        for (unsigned int i = 0; i < propertyCount; i++) {
            YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]];
            if (info.name) propertyInfos[info.name] = info;
        }
        free(properties);
    }
    
    unsigned int ivarCount = 0;
    Ivar *ivars = class_copyIvarList(cls, &ivarCount);
    if (ivars) {                        //解析ivar,并以name為key,進行緩存設置
        NSMutableDictionary *ivarInfos = [NSMutableDictionary new];
        _ivarInfos = ivarInfos;
        for (unsigned int i = 0; i < ivarCount; i++) {
            YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]];
            if (info.name) ivarInfos[info.name] = info;
        }
        free(ivars);
    }
    
    if (!_ivarInfos) _ivarInfos = @{};          //如果不存在相應的方法,則初始化空的dictionary給相應的方法
    if (!_methodInfos) _methodInfos = @{};
    if (!_propertyInfos) _propertyInfos = @{};
    
    _needUpdate = NO;                           //已經更新完成,設no
}

該函數雖然比較長,但也比較好理解,就是將method,property,ivar全部取出并附值給緩存。

實例

用YYModel測試用例來進行觀察:
YYTestNestRepo實現:

@interface YYTestNestRepo : NSObject
@property uint64_t repoID;
@property NSString *name;
@property YYTestNestUser *user;
@end
@implementation YYTestNestRepo
@end

YYTestNestRepo調用:

NSString *json = @"{\"repoID\":1234,\"name\":\"YYModel\",\"user\":{\"uid\":5678,\"name\":\"ibireme\"}}";
YYTestNestRepo *repo = [YYTestNestRepo yy_modelWithJSON:json];

設置解析斷點解析class YYTestNestRepo:

class YYTestNestRepo

至此,整個model的class信息全部被解析完成,然后設置到了YYClassInfo類型的上。

小結

相對于JSONModel只對Property進行解析然后緩存。
YYModel將Class的Method,Property,Ivar全部進行了解析與緩存。

其中比較亮點的地方:

  • 1.Ivar和property解析出來的YYEncodingType
  • 2.CFMutableDictionaryRef的緩存
  • 3.可以動態更新的needUpdate

參考資料

本文csdn地址
1.鄭欽洪_:YYModel 源碼歷險記
2.Objective-C Runtime Programming Guide - Declared Properties
3.Objective-C Runtime Programming Guide - Type Encodings
4.runtime源代碼
5.Objective-C Runtime的數據類型
6.輕松學習之三——IMP指針的作用
7.runtime Method
8.apple-objective_c runtime
9.Objective-C Runtime

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 我們常常會聽說 Objective-C 是一門動態語言,那么這個「動態」表現在哪呢?我想最主要的表現就是 Obje...
    Ethan_Struggle閱讀 2,231評論 0 7
  • 終于把前面的base文件夾簡簡單單的看了一遍,終于可以回到正片上來了,保證不爛尾。 項目天天用yymodel解析數...
    充滿活力的早晨閱讀 1,388評論 1 0
  • 如何集成? 支持CocoaPods,在 Podfile 中添加 pod 'YYModel'。 支持Carthage...
    松哥888閱讀 11,066評論 0 7
  • 天降雄才濟亂世, 不屑達貴隱山林。 辭王爵金淡泊志, 渭水獨釣樂逍遙。 莊子的思想:窮則獨善其身,達則兼濟天下。沒...
    歲月不饒人老不正經閱讀 177評論 0 1
  • 今天是端午放假第一天。我終于在家里的床上醒來,開始一天的時光。刷牙洗漱以后,跟著媽媽一起吃早飯,非常愜意的時光。于...
    小朱砂閱讀 299評論 0 0