本文章所使用的YYModel源码基于0.9.8版本。
从截图来看,YYModel是由两个类构成,本章先着手分析YYClassInfo
,该类比较简单,主要使用runtime
来获取类的属性、成员变量、方法的相关信息。
YYClassInfo
一共包含有四个类,如下图所示:- YYClassIvarInfo:类成员变量相关信息;
- YYClassMethodInfo:类方法相关信息;
- YYClassPropertyInfo:类属性相关信息;
- YYClassInfo:类相关信息,由上边三个类加一些其他信息组成;
YYClassIvarInfo
从类名可以看出该类存储了类成员变量的相关信息,该类由五条只读属性和一个实例化方法构成。五条属性如下图所示:
除了
YYEncodingType
类型的属性其它都是简单的调用runtime
方法取得的,那这个YYEncodingType
到底是什么呢?点击该类型可以看到其为一枚举类型,枚举了所有的类型编码、方法编码和属性关键字,关于类型编码和属性关键字的更多信息请查看官网或NSHipster。这里使用了NS_OPTIONS
而未使用NS_ENUM
,关于两者的区别请点击这里。type
属性的实现如下:
const char *typeEncoding = ivar_getTypeEncoding(ivar);
if (typeEncoding) {
_typeEncoding = [NSString stringWithUTF8String:typeEncoding];
_type = YYEncodingGetType(typeEncoding);
}
通过调用YYEncodingGetType
方法传入类型编码获得。
YYClassMethodInfo
该类存储了方法的相关信息,包括方法名、SEL、IMP、方法类型、返回值类型、参数类型数组。
// 方法结构体
_method = method;
// SEL
_sel = method_getName(method);
// IMP
_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);
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;
YYClassPropertyInfo
该类存储了属性的相关信息,包括属性结构体、属性名、编码类型、成员变量名、遵守的协议等。在类的实现中可以看到objc_property_attribute_t
结构体,点到头文件看看它的声明是这样的。
简单的包含了name和value。name属性名;value属性的值,通常为空。通过例子来解释下其意思,对于一个如下属性:
@property (nonatomic, copy) NSString *name;
其name和value为
T表示属性的类型;C表示Copy,N表示nonatomic,这两个是属性的修饰符;V表示属性所对应的成员变量。可以看到属性的修饰符通常是没有值的,包括retain,assgin,atomic等。在该类的实现中只有类型编码的分支较为复杂,下面分析一下:
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];
// 找不到 @" 停止本次循环
if (![scanner scanString:@"@\"" intoString:NULL]) continue;
// 属性的类
NSString *clsName = nil;
if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
}
// 属性所遵守的协议,属性可遵守多个协议,
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
的相应属性,如果没有定制的赋值和取值操作,手动实现一下。
if (_name.length) {
if (!_getter) {
_getter = NSSelectorFromString(_name);
}
if (!_setter) {
_setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
}
}
YYClassInfo
类的信息,其实就是上边三个类的一个集合加上一些其他的信息组成。
- (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;
}
关于_update
方法并没有什么好讲的,就是runtime的简单应用,稍微有一些基础的都能看懂,关于元类在runtime相关的文章中都能看到,已经被讲烂了。
+ (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];
}
// 发送信号
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
内部做了信号量处理,为线程安全的;当类的内部结构变化后,例如使用class_addMethod()
添加一个方法,你需要调用setNeedUpdate()
,在needUpdate
返回YES之后重新调用``+ (instancetype)classInfoWithClass:(Class)cls`来获取类的最新信息。