美文网首页
YYModel源码分析(一)

YYModel源码分析(一)

作者: fire_fire | 来源:发表于2016-06-21 10:47 被阅读175次

    本文章所使用的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`来获取类的最新信息。

    相关文章

      网友评论

          本文标题:YYModel源码分析(一)

          本文链接:https://www.haomeiwen.com/subject/bupwdttx.html