《YYModel源码分析(一)YYClassInfo》

2,931

YYModel大家肯定很熟悉,其非侵入性,易用性都使得它成为json-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"如果遵循了多个协议是这样的@"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

那么其实上面最重要的三部分都看完了,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我们下一章再说。小弟不才,如有误区请一定及时指出。