YYModel源码分析(一)

本文章所使用的YYModel源码基于0.9.8版本。
从截图来看,YYModel是由两个类构成,本章先着手分析YYClassInfo,该类比较简单,主要使用runtime来获取类的属性、成员变量、方法的相关信息。

DingTalk20160620171225.png

YYClassInfo一共包含有四个类,如下图所示:
DingTalk20160620171130.png

  • YYClassIvarInfo:类成员变量相关信息;
  • YYClassMethodInfo:类方法相关信息;
  • YYClassPropertyInfo:类属性相关信息;
  • YYClassInfo:类相关信息,由上边三个类加一些其他信息组成;

YYClassIvarInfo

从类名可以看出该类存储了类成员变量的相关信息,该类由五条只读属性和一个实例化方法构成。五条属性如下图所示:

DingTalk20160620184733.png

除了YYEncodingType类型的属性其它都是简单的调用runtime方法取得的,那这个YYEncodingType到底是什么呢?点击该类型可以看到其为一枚举类型,枚举了所有的类型编码、方法编码和属性关键字,关于类型编码和属性关键字的更多信息请查看官网NSHipster。这里使用了NS_OPTIONS而未使用NS_ENUM,关于两者的区别请点击这里type属性的实现如下:

 const char *typeEncoding = ivar_getTypeEncoding(ivar);
 if (typeEncoding) {
     _typeEncoding = [NSString stringWithUTF8String:typeEncoding];
     _type = YYEncodingGetType(typeEncoding);
 }

通过调用YYEncodingGetType方法传入类型编码获得。

YYClassMethodInfo

该类存储了方法的相关信息,包括方法名、SEL、IMP、方法类型、返回值类型、参数类型数组。

    // 方法结构体
    _method = method;
    // SEL
    _sel = method_getName(method);
    // IMP
    _imp = method_getImplementation(method);
    // 方法名
    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);
    }
    // 方法的参数
    unsigned int argumentCount = method_getNumberOfArguments(method);
    if (argumentCount > 0) {
        NSMutableArray *argumentTypes = [NSMutableArray new];
        for (unsigned int i = 0; i < argumentCount; i++) {
            char *argumentType = method_copyArgumentType(method, i);
            NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil;
            [argumentTypes addObject:type ? type : @""];
            if (argumentType) free(argumentType);
        }
        _argumentTypeEncodings = argumentTypes;

YYClassPropertyInfo

该类存储了属性的相关信息,包括属性结构体、属性名、编码类型、成员变量名、遵守的协议等。在类的实现中可以看到objc_property_attribute_t结构体,点到头文件看看它的声明是这样的。

DingTalk20160621095431.png

简单的包含了name和value。name属性名;value属性的值,通常为空。通过例子来解释下其意思,对于一个如下属性:

@property (nonatomic, copy) NSString *name;

其name和value为

DingTalk20160621100421.png

T表示属性的类型;C表示Copy,N表示nonatomic,这两个是属性的修饰符;V表示属性所对应的成员变量。可以看到属性的修饰符通常是没有值的,包括retain,assgin,atomic等。在该类的实现中只有类型编码的分支较为复杂,下面分析一下:

if (attrs[i].value) {
    // 类型编码
    _typeEncoding = [NSString stringWithUTF8String:attrs[i].value];
    type = YYEncodingGetType(attrs[i].value);
    // 如果属性类型为对象
    if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) {
        // 扫描属性类型字符串
        NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];
        // 找不到 @" 停止本次循环
        if (![scanner scanString:@"@\"" intoString:NULL]) continue;
        
        // 属性的类
        NSString *clsName = nil;
        if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
            if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
        }
        
        // 属性所遵守的协议,属性可遵守多个协议,
        NSMutableArray *protocols = nil;
        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;
    }
}

还有比较重要的setter和getter赋值。

case 'G': {
    type |= YYEncodingTypePropertyCustomGetter;
    if (attrs[i].value) {
        _getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
    }
} break;
case 'S': {
    type |= YYEncodingTypePropertyCustomSetter;
    if (attrs[i].value) {
        _setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
    }
} 

如果有定制的setter和getter方法,直接赋值给YYClassPropertyInfo的相应属性,如果没有定制的赋值和取值操作,手动实现一下。

if (_name.length) {
    if (!_getter) {
        _getter = NSSelectorFromString(_name);
    }
    if (!_setter) {
        _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
    }
}

YYClassInfo

类的信息,其实就是上边三个类的一个集合加上一些其他的信息组成。

- (instancetype)initWithClass:(Class)cls {
    if (!cls) return nil;
    self = [super init];
    // 类
    _cls = cls;
    // 父类
    _superCls = class_getSuperclass(cls);
    // 是否元类
    _isMeta = class_isMetaClass(cls);
    if (!_isMeta) {
        // 元类
        _metaCls = objc_getMetaClass(class_getName(cls));
    }
    // 类名
    _name = NSStringFromClass(cls);
    // 获取本类信息
    [self _update];
    // 父类信息
    _superClassInfo = [self.class classInfoWithClass:_superCls];
    return self;
}

关于_update方法并没有什么好讲的,就是runtime的简单应用,稍微有一些基础的都能看懂,关于元类在runtime相关的文章中都能看到,已经被讲烂了。

+ (instancetype)classInfoWithClass:(Class)cls {
    if (!cls) return nil;
    // 类信息缓存,Class为key,YYClassInfo为value
    static CFMutableDictionaryRef classCache;
    // 元类信息缓存,Class为key,YYClassInfo为value
    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));
    // info存在且需要更新
    if (info && info->_needUpdate) {
        [info _update];
    }
    // 发送信号
    dispatch_semaphore_signal(lock);
    if (!info) {
        info = [[YYClassInfo alloc] initWithClass:cls];
        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;
}

类信息和元类信息都做了缓存字典,key为Class,value为YYClassInfo;+ (instancetype)classInfoWithClass:(Class)cls内部做了信号量处理,为线程安全的;当类的内部结构变化后,例如使用class_addMethod()添加一个方法,你需要调用setNeedUpdate(),在needUpdate返回YES之后重新调用``+ (instancetype)classInfoWithClass:(Class)cls`来获取类的最新信息。

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

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,517评论 18 399
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,525评论 33 466
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,810评论 6 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,517评论 18 139
  • 最近用了一个星期的时间阅读了这本《赖声川的创意学》,市面上大多讲创意的书都是讲技巧,唯有这本书讲的是创意的本质。此...
    一叶知秋V阅读 491评论 0 1