本文章所使用的YYModel源碼基于0.9.8版本鼓黔。
從截圖來看,YYModel是由兩個類構(gòu)成不见,本章先著手分析YYClassInfo
澳化,該類比較簡單,主要使用runtime
來獲取類的屬性稳吮、成員變量缎谷、方法的相關(guān)信息。
YYClassInfo
一共包含有四個類灶似,如下圖所示:- YYClassIvarInfo:類成員變量相關(guān)信息列林;
- YYClassMethodInfo:類方法相關(guān)信息;
- YYClassPropertyInfo:類屬性相關(guān)信息酪惭;
- YYClassInfo:類相關(guān)信息希痴,由上邊三個類加一些其他信息組成;
YYClassIvarInfo
從類名可以看出該類存儲了類成員變量的相關(guān)信息春感,該類由五條只讀屬性和一個實(shí)例化方法構(gòu)成砌创。五條屬性如下圖所示:
除了
YYEncodingType
類型的屬性其它都是簡單的調(diào)用runtime
方法取得的,那這個YYEncodingType
到底是什么呢鲫懒?點(diǎn)擊該類型可以看到其為一枚舉類型纺铭,枚舉了所有的類型編碼、方法編碼和屬性關(guān)鍵字刀疙,關(guān)于類型編碼和屬性關(guān)鍵字的更多信息請查看官網(wǎng)或NSHipster。這里使用了NS_OPTIONS
而未使用NS_ENUM
扫倡,關(guān)于兩者的區(qū)別請點(diǎn)擊這里谦秧。type
屬性的實(shí)現(xiàn)如下:
const char *typeEncoding = ivar_getTypeEncoding(ivar);
if (typeEncoding) {
_typeEncoding = [NSString stringWithUTF8String:typeEncoding];
_type = YYEncodingGetType(typeEncoding);
}
通過調(diào)用YYEncodingGetType
方法傳入類型編碼獲得竟纳。
YYClassMethodInfo
該類存儲了方法的相關(guān)信息,包括方法名疚鲤、SEL锥累、IMP、方法類型集歇、返回值類型桶略、參數(shù)類型數(shù)組。
// 方法結(jié)構(gòu)體
_method = method;
// SEL
_sel = method_getName(method);
// IMP
_imp = method_getImplementation(method);
// 方法名
const char *name = sel_getName(_sel);
if (name) {
_name = [NSString stringWithUTF8String:name];
}
// 方法的參數(shù)和返回類型
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);
}
// 方法的參數(shù)
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;
YYClassPropertyInfo
該類存儲了屬性的相關(guān)信息诲宇,包括屬性結(jié)構(gòu)體际歼、屬性名、編碼類型姑蓝、成員變量名鹅心、遵守的協(xié)議等。在類的實(shí)現(xiàn)中可以看到objc_property_attribute_t
結(jié)構(gòu)體纺荧,點(diǎn)到頭文件看看它的聲明是這樣的旭愧。
簡單的包含了name和value。name屬性名宙暇;value屬性的值输枯,通常為空。通過例子來解釋下其意思占贫,對于一個如下屬性:
@property (nonatomic, copy) NSString *name;
其name和value為
T表示屬性的類型桃熄;C表示Copy,N表示nonatomic靶剑,這兩個是屬性的修飾符蜻拨;V表示屬性所對應(yīng)的成員變量∽可以看到屬性的修飾符通常是沒有值的缎讼,包括retain,assgin坑匠,atomic等血崭。在該類的實(shí)現(xiàn)中只有類型編碼的分支較為復(fù)雜,下面分析一下:
if (attrs[i].value) {
// 類型編碼
_typeEncoding = [NSString stringWithUTF8String:attrs[i].value];
type = YYEncodingGetType(attrs[i].value);
// 如果屬性類型為對象
if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) {
// 掃描屬性類型字符串
NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];
// 找不到 @" 停止本次循環(huán)
if (![scanner scanString:@"@\"" intoString:NULL]) continue;
// 屬性的類
NSString *clsName = nil;
if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
}
// 屬性所遵守的協(xié)議厘灼,屬性可遵守多個協(xié)議座掘,
NSMutableArray *protocols = nil;
while ([scanner scanString:@"<" intoString:NULL]) {
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;
}
}
還有比較重要的setter和getter賦值绰寞。
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]);
}
}
如果有定制的setter和getter方法,直接賦值給YYClassPropertyInfo
的相應(yīng)屬性,如果沒有定制的賦值和取值操作邓馒,手動實(shí)現(xiàn)一下。
if (_name.length) {
if (!_getter) {
_getter = NSSelectorFromString(_name);
}
if (!_setter) {
_setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
}
}
YYClassInfo
類的信息怕篷,其實(shí)就是上邊三個類的一個集合加上一些其他的信息組成。
- (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;
}
關(guān)于_update
方法并沒有什么好講的钻洒,就是runtime的簡單應(yīng)用,稍微有一些基礎(chǔ)的都能看懂锄开,關(guān)于元類在runtime相關(guān)的文章中都能看到素标,已經(jīng)被講爛了。
+ (instancetype)classInfoWithClass:(Class)cls {
if (!cls) return nil;
// 類信息緩存萍悴,Class為key头遭,YYClassInfo為value
static CFMutableDictionaryRef classCache;
// 元類信息緩存,Class為key癣诱,YYClassInfo為value
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);
lock = dispatch_semaphore_create(1);
});
// 等待信號
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
// info存在且需要更新
if (info && info->_needUpdate) {
[info _update];
}
// 發(fā)送信號
dispatch_semaphore_signal(lock);
if (!info) {
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;
}
類信息和元類信息都做了緩存字典计维,key為Class,value為YYClassInfo狡刘;+ (instancetype)classInfoWithClass:(Class)cls
內(nèi)部做了信號量處理享潜,為線程安全的;當(dāng)類的內(nèi)部結(jié)構(gòu)變化后嗅蔬,例如使用class_addMethod()
添加一個方法剑按,你需要調(diào)用setNeedUpdate()
,在needUpdate
返回YES之后重新調(diào)用``+ (instancetype)classInfoWithClass:(Class)cls`來獲取類的最新信息澜术。