前言
本文的中文注釋代碼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的了解,需要知道一個很重要的概念:
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變量處:
(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
方法:
對于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:
(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指針來說,網上有一個特別多轉載的圖:
上圖實線是 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:
至此,整個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