概述
YYModel是封装的一个用来实现JSON格式数据和Model互转的强大库。相比较其他库来说,例如JSONModel、MJExtensions来说,性能更高一些,本文旨在分析YYModel的代码,通过实际案例来进一步说明。
文件结构
相信使用过MJExtensions的iOS开发者来说,接触YYModel并不是很陌生,因为两者在某些思路上是相似的,例如都用了category的方式来实现,Protocol中的方法都有着相似的功能,例如黑白名单、Property名称和dictionary中key中的对应关系等。YYModel的文件包括:
- YYModel.h
- YYClassInfo.h、YYClassInfo.m
- NSObject+YYModel.h、NSObject+YYModel.m
其中YYModel.h是公共头文件、YYClassInfo是针对NSObject的Ivar、Property、Method进行封装的类、NSObject+YYModel是具体负责转JSON和model的category。本篇分析一下YYClassInfo。
YYClassInfo
查看代码发现,NSObject对象中定义一个指针isa,如下:
@interface NSObject {
Class isa OBJC_ISA_AVAILABILITY;
}
isa指向Class类型,Class类型是objc_class的别名,而objc_class是一个结构体,如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
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;
主要信息包含:isa指向Class的meta类、super_class指向Class的父类。具体关系可以参考下图:
name是名称、objc_ivar_list是成员变量链表、methodLists是成员方法链表、protocols是附属协议链表。关于Class结构和runtime的文档,可以参考苹果官方文档和http://www.cocoachina.com/ios/20141031/10105.html。
YYClassInfo封装了Class对象的各个元素,代码如下:
@interface YYClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls; //关联的Class对象
@property (nullable, nonatomic, assign, readonly) Class superCls; //父类
@property (nullable, nonatomic, assign, readonly) Class metaCls; //元类
@property (nonatomic, readonly) BOOL isMeta; //是否是元类
@property (nonatomic, strong, readonly) NSString *name; //Class名称
@property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; //父类Class对应的YYClassInfo
@property (nullable, nonatomic, strong, readonly) NSDictionary *ivarInfos; //ivar键值对
@property (nullable, nonatomic, strong, readonly) NSDictionary *methodInfos; //方法键值对
@property (nullable, nonatomic, strong, readonly) NSDictionary *propertyInfos; //附属协议键值对
@end
YYClassInfo对象通过classInfoWithClass:方法根据Class创建一个YYClassInfo对象。代码注释如下:
+ (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);
lock = dispatch_semaphore_create(1);
});
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
if (info && info->_needUpdate) {
[info _update];
}
dispatch_semaphore_signal(lock);
if (!info) {
info = [[YYClassInfo alloc] initWithClass:cls]; //创建YYClassInfo对象
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;
}
该方法用了一个键值对来存储创建好的YYClassInfo对象,key是Class对象,value是classInfo对象,当多次调用classInfoWithClass:方法时,如果Class对象相同,则直接从缓存中取出classInfo对象,不用每次都创建一个新的classInfo对象,提高了性能。同时检查_needUpdate变量,如有,刷新classInfo对象的内部属性值。
_needUpdate是YYClassInfo的一个成员变量,当Class的信息更新时,设置_needUpdate为YES,表示需要更新YYClassInfo。
- (void)setNeedUpdate { //设置需要更新
_needUpdate = YES;
}
- (BOOL)needUpdate { //返回是否需要更新
return _needUpdate;
}
如果_needUpdate设置为YES,则调用_update方法进行更新。第一次创建YYClassInfo对象时,会去调用一次_update方法,_update方法负责构建YYClassInfo中的ivarInfos、methodInfos和propertyInfos,这三个键值对分别维护了YYClassIvarInfo、YYClassMethodInfo、YYClassPropertyInfo信息,代码注释如下:
- (void)_update {
_ivarInfos = nil;
_methodInfos = nil;
_propertyInfos = nil;
Class cls = self.cls;
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(cls, &methodCount); //获取Method数组
if (methods) {
NSMutableDictionary *methodInfos = [NSMutableDictionary new];
_methodInfos = methodInfos;
for (unsigned int i = 0; i < methodCount; i++) {
YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]]; //用YYClassMethodInfo封装Method中的信息
if (info.name) methodInfos[info.name] = info; //存入methodInfos
}
free(methods);
}
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(cls, &propertyCount); //获取objc_property_t数组
if (properties) {
NSMutableDictionary *propertyInfos = [NSMutableDictionary new];
_propertyInfos = propertyInfos;
for (unsigned int i = 0; i < propertyCount; i++) {
YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]]; //用YYClassPropertyInfo封装objc_property_t中的信息
if (info.name) propertyInfos[info.name] = info; //存入propertyInfos
}
free(properties);
}
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(cls, &ivarCount); //获取Ivar数组
if (ivars) {
NSMutableDictionary *ivarInfos = [NSMutableDictionary new];
_ivarInfos = ivarInfos;
for (unsigned int i = 0; i < ivarCount; i++) {
YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]]; //用YYClassIvarInfo封装Ivar中的信息
if (info.name) ivarInfos[info.name] = info; //存入ivarInfos
}
free(ivars);
}
if (!_ivarInfos) _ivarInfos = @{};
if (!_methodInfos) _methodInfos = @{};
if (!_propertyInfos) _propertyInfos = @{};
_needUpdate = NO;
}
例如定义一个Student的类,如下:
@interface College : NSObject
@property (nonatomic, copy) NSString *name;
@end
@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) College *college;
@end
Student *student = [[Student alloc] init];
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:[student class]];
通过YYClassInfo的初始化方法classInfoWithClass创建,得到对象如下:
下面分析一下ivarInfos、methodInfos和propertyInfos中存储的对象:
-
YYClassIvarInfo
通过runtime的class_copyIvarList方法得到Ivar数组, Ivar是iOS中描述成员变量的数据结构,如下:
typedef struct objc_ivar *Ivar; 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来封装Ivar中的信息,如下:
@interface YYClassIvarInfo : NSObject @property (nonatomic, assign, readonly) Ivar ivar; //关联的Ivar变量 @property (nonatomic, strong, readonly) NSString *name; //成员变量名 @property (nonatomic, assign, readonly) ptrdiff_t offset; //成员变量偏移值 @property (nonatomic, strong, readonly) NSString *typeEncoding; //成员变量类型编码 @property (nonatomic, assign, readonly) YYEncodingType type; //类型枚举 @end
initWithIvar:方法通过Ivar来构建YYClassIvarInfo对象,注释如下:
- (instancetype)initWithIvar:(Ivar)ivar { if (!ivar) return nil; self = [super init]; _ivar = ivar; //关联Ivar对象 const char *name = ivar_getName(ivar); //获取名称 if (name) { _name = [NSString stringWithUTF8String:name]; } _offset = ivar_getOffset(ivar); //获取偏移值 const char *typeEncoding = ivar_getTypeEncoding(ivar); //获取Ivar的类型编码 if (typeEncoding) { //如果有类型编码 _typeEncoding = [NSString stringWithUTF8String:typeEncoding]; _type = YYEncodingGetType(typeEncoding); //根据类型编码查找对应类型枚举 } return self; }
typeEncoding是iOS规定的一套类型编码,用不同的字符表示不同的类型,例如@表示对象类型,B表示bool类型,具体参考苹果官方文档。通过YYEncodingGetType方法可以通过类型编码映射到对应的枚举。例如student的成员变量name对应如下:
-
YYClassMethodInfo
通过class_copyMethodList方法得到Method数组,Method是iOS中描述成员方法数据结构objc_method,如下:
struct objc_method { SEL method_name OBJC2_UNAVAILABLE; //方法名 char *method_types OBJC2_UNAVAILABLE; //方法参数 IMP method_imp OBJC2_UNAVAILABLE; //方法的实现 }
里面主要包含一个SEL和IMP,SEL是方法的标识符,IMP是真正实现方法的函数指针,OC在调用对象的方法时,转化如下:
[student mark]; ->转化为objc_msgSend(id, SEL, ...) //第一个参数是student对象,第二个参数是SEL类型的@selector(mark),后续参数是mark方法要传的参数。 ->遍历Class对象的method数组,通过selector找到相应Method对应的IMP,调用IMP,并将参数传入。
用YYClassMethodInfo来封装Method中的信息,如下:
@interface YYClassMethodInfo : NSObject @property (nonatomic, assign, readonly) Method method; //关联的Method变量 @property (nonatomic, strong, readonly) NSString *name; //成员方法名 @property (nonatomic, assign, readonly) SEL sel; //SEL标识符 @property (nonatomic, assign, readonly) IMP imp; //IMP指针 @property (nonatomic, strong, readonly) NSString *typeEncoding;//类型编码 @property (nonatomic, strong, readonly) NSString *returnTypeEncoding; //方法返回类型编码 @property (nullable, nonatomic, strong, readonly) NSArray
*argumentTypeEncodings; //方法参数类型编码数组 @end initWithMethod:方法通过Method构建YYClassMethodInfo,代码注释如下:
- (instancetype)initWithMethod:(Method)method { if (!method) return nil; self = [super init]; _method = method; //关联Method _sel = method_getName(method); //获取SEL _imp = method_getImplementation(method); //获取IMP 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); } ... return self; }
由于[a bb:cc]在运行时会被转化为objc_msgSend(a, @selector(bb),cc),第一个参数表示发送消息的目的对象,第二个参数表示消息的SEL标识,第三个参数为入参cc。所以Method的typeEncoding除了包含返回值类型和bb:方法的参数类型,还多包含前两个参数。
例如Student的name的setter方法,如下:
typeEncoding共4个参数,第一个编码是"v"(返回值是void类型)、第二个编码是"@"(发送消息的目的对象是NSObject类型),第三个编码是":"(类型是SEL),第四个是编码是"@"(setName:的参数是NSString类型)。
-
YYClassPropertyInfo
通过class_copyPropertyList方法得到objc_property_t类型的数组,objc_property_t是描述Class对象property属性的结构,YYClassPropertyInfo封装objc_property_t相关信息。
@interface YYClassPropertyInfo : NSObject @property (nonatomic, assign, readonly) objc_property_t property; //关联的objc_property_t @property (nonatomic, strong, readonly) NSString *name; //property名 @property (nonatomic, assign, readonly) YYEncodingType type; //property的类型 @property (nonatomic, strong, readonly) NSString *typeEncoding; //类型编码 @property (nonatomic, strong, readonly) NSString *ivarName; //Ivar名 @property (nullable, nonatomic, assign, readonly) Class cls; // @property (nullable, nonatomic, strong, readonly) NSArray
*protocols; // @property (nonatomic, assign, readonly) SEL getter; //getter方法对应的SEL @property (nonatomic, assign, readonly) SEL setter; //setter方法对应的SEL @end initWithProperty:方法通过objc_property_t创建YYClassPropertyInfo对象,代码注释如下:
- (instancetype)initWithProperty:(objc_property_t)property { ... _property = property; //关联objc_property_t 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的参数数组, //遍历attrs数组,获取相关信息,例如 for (unsigned int i = 0; i < attrCount; i++) { switch (attrs[i].name[0]) { case 'T': {...} break; //T对应typeEncoding case 'V': { //V对应ivar值 if (attrs[i].value) { _ivarName = [NSString stringWithUTF8String:attrs[i].value]; } } break; ... //"R、C、&、N"等property的修饰器类型 } } }
通过property_copyAttributeList方法获取attrs数组,每个元素是objc_property_attribute_t类型的结构,如下:
typedef struct { const char *name; //标识 const char *value; //值 } objc_property_attribute_t;
name的标识不同,例如T、V等,对应的value有不同含义。例如Student的name属性,如下:
_typeEncoding的前缀是@,因为name是NSString类型,注意这里的_name表示property的名称,而_ivarName表示property对应的成员变量的名称,_getter和_setter分别是property生成的方法。
总结
学习YYClassInfo对于了解iOS的Class结构和runtime有一定帮助。对于我来说,发现在写的过程中,自己还有很多理解不到位的地方。所以写文章能够促进自己更加仔细的阅读源代码,文中如有不足之处,希望各位大神指正。
参考资料
YYModel源代码分析
Objective-C Runtime 运行时之二:成员变量与属性
苹果官方文档 Type Encodings