YYModel你们确定很熟悉,其非侵入性,易用性都使得它成为json-Model的新宠,接下来我们分析下他的原理。html
先看YYClassInfo这个类,他是一个runtime中Class在OC层的封装,而且解析增长了不少描述,因此想了解YYModel原理必须对runtime有必定了解。json
在runtime层类型实际上是一个结构体objc_class,objc_class中存储着指向超类的superClass、指向所属类型的ISA、指向class_rw_t的指针,class_rw_t中存储着这个类的成员变量列表、属性列表和方法列表。因此其实咱们是能够经过runtime的api去读取类的这些信息的。api
编译器会以必定规则对类型进行编码,而且存储在runtime数据结构中,因此咱们能够根据规则解析出属性、成员变量和方法参数的类型 《官方文档》数组
YYEncodingType 表明的是typeEncoding所表明的类型。经过YYEncodingType YYEncodingGetType(const char *typeEncoding)
方法能够将字符串转换成一个表明具体类型的枚举值。 YYEncodingType不单单表明类型,他是一个按位枚举,还存储类一些属性描述信息。缓存
成员变量在runtime层表现为Ivar这个类型,经过Ivar能够读取变量名,等等信息bash
@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() //获取成员变量的类型编码
复制代码
methodInfo中包含的信息要多一点app
@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有ide
method_getName()
method_getImplementation()
method_getTypeEncoding()
method_copyReturnMethod()
method_getNumberOfArguments() //获取参数数量
method_copyArgumentType(method,i) //获取方法第I个参数的类型编码
复制代码
咱们先分析一下类的属性中包含什么样的信息,包含了成员变量、遵循的协议、还有描述属性的关键字例如内存管理方面的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"若是遵循了多个协议是这样的@"NSString",因此以上代码也是依据与此展开的。 咱们再看一下如何获取的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就很简单了
@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咱们下一章再说。小弟不才,若有误区请必定及时指出。