《YYModel源码分析(一)YYClassInfo》

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我们下一章再说。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270