YYModel大家肯定很熟悉,其非侵入性,易用性都使得它成为josn-Model的新宠,接下来咱们分析下他的原理。
必须要了解的知识
-
先看YYClassInfo这个类,他是一个runtime中Class在OC层的封装,并且解析增加了很多描述,所以想了解YYModel原理必须对runtime有一定了解。
-
在runtime层类型其实是一个结构体objc_class,objc_class中存储着指向超类的superClass、指向所属类型的ISA、指向class_rw_t的指针,class_rw_t中存储着这个类的成员变量列表、属性列表和方法列表。所以其实我们是可以通过runtime的api去读取类的这些信息的。
-
编译器会以一定规则对类型进行编码,并且存储在runtime数据结构中,所以我们可以根据规则解析出属性、成员变量和方法参数的类型 《官方文档》
YYEncodingType
YYEncodingType 代表的是typeEncoding所代表的类型。通过YYEncodingType YYEncodingGetType(const char *typeEncoding)
方法可以将字符串转换成一个代表具体类型的枚举值。
YYEncodingType不仅仅代表类型,他是一个按位枚举,还存储类一些属性描述信息。
YYClassIvarInfo
成员变量在runtime层表现为Ivar这个类型,通过Ivar可以读取变量名,等等信息
@interface YYClassIvarInfo : NSObject
//注意这里用assign修饰了,因为runtime层的数据结构都不归引用计数管理
@property (nonatomic, assign, readonly) Ivar ivar; ///runtime中成员变量
@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; //由类型编码解析出的信息
//解析Ivar信息
-(instancetype)initWithIvar:(Ivar)ivar;
@end
这个类中主要用到的runtime接口有
ivar_getName() //获取成员变量名
ivar_getOffset() //获取偏移量
ivar_getTypeEncoding() //获取成员变量的类型编码
YYClassMethodInfo
methodInfo中包含的信息要多一点
@interface YYClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) Method method; //runtime Method数据
@property (nonatomic, assign, readonly) NSString * name; //方法名
@property (nonatomic, assign, readonly) SEL sel; //选择器
@property (nonatomic, assign, readonly) IMP imp; //函数指针
@property (nonatomic, strong, readonly) NSString * typeEncoding; //方法的类型编码
@property (nonatomic, strong, readonly) NSString * returnTypeEncoding; //返回值的类型编码
@property (nonatomic, nullable, strong, readonly) NSArray<NSString *> *argumentTypeEncoding; //参数类型数组,用数组表示
- (instancetype)initWithMethod:(Method)method;
@end
主要用到的runtimeApi有
method_getName()
method_getImplementation()
method_getTypeEncoding()
method_copyReturnMethod()
method_getNumberOfArguments() //获取参数数量
method_copyArgumentType(method,i) //获取方法第I个参数的类型编码
YYClassPropertyInfo
我们先分析一下类的属性中包含什么样的信息,包含了成员变量、遵循的协议、还有描述属性的关键字例如内存管理方面的copy、strong、weak等,还要读写的readOnly等。还有默认生成的setter和getter方法。这些都需要解析出来
@interface YYClassPropertyInfo : NSObject
@property (nonatomic, assign, readonly) objc_property_t property; //runtime中属性数据
@property (nonatomic, strong, readonly) NSString * name; //属性名
@property (nonatomic, assign, readonly) YYEncodingType type; //类型枚举
@property (nonatomic, strong, readonly) NSString * typeEncoding; //类型编码
@property (nonatomic, strong, readonly) NSString * ivarName; //成员变量名字
@property (nonatomic, nullable, assign, readonly) Class cls; //所属类型,这里需要直接解析出来
@property (nonatomic, nullable, strong, readonly) NSArray<NSString *> *protocols; //遵循的协议
@property (nonatomic, assign, readonly) SEL getter; //生成的getter方法
@property (nonatomic, assign, readonly) SEL setter; //生成的setter方法
- (instancetype)initWithProperty:(objc_property_t)property;
@end
属性的解析过程是这里面最长的,因为涉及到encoding字符串的解析。比如类型的解析。我们可以通过property_copyAttributeList
方法获取属性的描述objc_property_attribute_t数据结构大概是一个数组,数组的元素是一个map,而且不用的key对应的是不同的含义,比如"T"代表的属性的类型编码,"V"代表的是成员变量的名字,等等。我们着重看一下解析类型和协议的位置。
case 'T'://代表type encoding
if (attrs[i].value){
_typeEncoding = [NSString stringWithUTF8String:attrs[i].value];
//转成YYEncodingType
type = YYEncodingGetType(attrs[i].value);
//如果类型是oc对象,把OC对象解析出来,例如:@"NSString"
if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length){
NSScanner * scanner = [NSScanner scannerWithString:_typeEncoding];
//先把扫描位置移动到"
if (![scanner scanString:@"@\"" intoString:NULL]) continue;
NSString *clsName = nil;
//然后三秒至存在"或者<的位置,这是因为如果遵循了协议typeEncode就是@"NSString<NSCopy>"了
if ([scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]){
if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
}
NSMutableArray *protocols = nil;
//如果遵循多个协议typecoding是这样的@"NSString<NSCopy><NSObject>",所以将扫描位置移动到<位置,循环截取
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;
}
}
}
break;
一个OC类型编码之后是这样的以NSString为例,@"NSString"
,如果遵循了协议是这样的,@"NSString<NSCopy>"如果遵循了多个协议是这样的@"NSString<NSCopy><NSObject>",所以以上代码也是依据与此展开的。
我们再看一下如何获取的get和set方法
if (_name.length){
if (!_getter){
_getter = NSSelectorFromString(_name);
}
if (!_setter){
_setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@",[_name substringFromIndex:1].uppercaseString,[_name substringFromIndex:1]]);
}
}
这个很简单其实就是根据属性名,首字符大些,然后在前面加上get和set,哈哈,是不是很厉害。
YYClassInfo
那么其实上面最重要的三部分都看完了,YYClassInfo就很简单了
@interface YYClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls; //所属类型
@property (nullable, nonatomic, assign, readonly) Class superCls; //超类
@property (nullable, nonatomic, assign, readonly) Class metaCls; //元类
@property (nonatomic, readonly) BOOL isMetal; //是否是元类
@property (nonatomic, strong, readonly) NSString * name; //类名
@property (nullable, nonatomic, strong, readonly) YYClassInfo * superClassInfo; //超类的classInfo
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; //属性集合,以字典的形式存储,key是成员变量名
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; //方法集合,以字典形式存储,key是方法名
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; //属性名,以字典形式存储,key是属性名
//设置需要update类信息。
- (void)setNeedUpdate;
//获取是否需要update类信息
- (BOOL)needUpdate;
//根据cls获取解析数据YYClassInfo
+ (nullable instancetype)classInfoWithClass:(Class)cls;
//根据className获取解析数据YYClassInfo
+ (nullable instancetype)classInfoWithClassName:(NSString *)className;
@end
那么我们通过classInfo解析一个类型都经过了哪些过程呢,首先会从缓存中读取是否有缓存数据,这个缓存是一个静态全局变量,如果缓存中有判断是否需要更新类数据,如果需要更新重新解析,如果缓存中没有数据,那么解析类数据,然后递归解析超类数据,直到超类为nil,NSObject的superClass就为nil。这个地方看似需要递归很多,但是我们通常的model都是直接继承自NSObject的,所以基本就两次左右。
我们看一下核心代码,基本都是调用的runtimeApi
- (void)_update{
_ivarInfos = nil;
_methodInfos = nil;
_propertyInfos = nil;
Class cls = self.cls;
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(cls, &methodCount);
if (methods){
NSMutableDictionary * methodInfo = [NSMutableDictionary new];
_methodInfos = _methodInfos;
for (unsigned int i = 0; i < methodCount; i++){
YYClassMethodInfo * info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]];
if (info.name) methodInfo[info.name] = info;
}
free(methods);
}
unsigned int propertyCount = 0;
objc_property_t * properties = class_copyPropertyList(cls, &propertyCount);
if (properties){
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){
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 = @{};
if (!_methodInfos) _methodInfos = @{};
if (!_propertyInfos) _propertyInfos = @{};
_needUpdate = NO;
}
总结
由YYClassInfo我们能看出作者对runtime的理解之深,通过对runtime类结构的封装,我们可以方便的获取到一个类的各种信息。json转model也就没有那么难了,关于NSObject+YYModel我们下一章再说。
网友评论