1. 预备知识
首先每一个instance即实例对象都有一根指针叫isa_t指针,指向对象对应的class,每一个class都有一个对应的isa_t指针指向class对应的元类。为什么要搞出元类这个东西呢? 关于这个问题我们自己不难理解,我们可以想一下我们每次调用一个类的实例方法和类方法,这些方法都分别保存在哪个地方呢?是不是实例方法就保存在实例中,类方法就保存在类中呢?
其实我们只要仔细想一下就发现这种方式是行不通的,每一个实例中的实例方法都试一样的,如果每一个实例都要保存一次方法,那岂不是造成了相当多的资源浪费? 于是就有了这么一个东西,下一层的方法保存在上一层中,我们只要把实例方法保存在class中,类方法保存在类的元类中,这样每次要找对应的方法,直接去找对应的上一级,这样更加高效而且更加节省资源。关于详细的资料请参考参考资料
2. YYClassInfo
YYClassInfo是对Class的一个封装,将class一些常用的信息进行了缓存,提高了命中率和访问效率,我们先看源码来理解
1 2 3 4 5 |
/** class的描述信息.对一个class进行封装 */ @interface YYClassInfo : NSObject @property (nonatomic, assign, readonly) Class cls; /// *ivarInfos; /// *methodInfos; /// *propertyInfos; /// |
在YYClassInfo声明的方法里,我们可以看到YYClassInfo一旦class进行了修改,就要做对应的更新,这样的操作是为了能够高效缓存class的信息,如ivars,method,property,我们首先从这个类的入口方法开始看起
2.1 入口
1 2 3 4 5 6 7 |
// 创建入口,从一个className中获取Class来获取class内容 + (instancetype)classInfoWithClassName:(NSString *)className { // 取得className对应的class Class cls = NSClassFromString(className); // 调用classInfoWithClass return [self classInfoWithClass:cls]; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// 通过class获取对应class的信息,并且对这些信息进行处理 + (instancetype)classInfoWithClass:(Class)cls { if (!cls) return nil; // class 缓存容器 static CFMutableDictionaryRef classCache; // metaclass 缓存容器 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); // 是元类还是Class,从结果的容器中进行查找 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不在缓存容器中 // 通过initWithClass重新创建一个info info = [[YYClassInfo alloc] initWithClass:cls]; if (info) { // 缓存到对应的容器中 dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); // 再一次判断是meta还是class CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info)); dispatch_semaphore_signal(lock); } } return info; } |
这里有几点需要说明一下:
- mutable不是线程安全的,所以这里需要创建锁
- 创建缓存容器,如果在缓存容器中直接找到了class,则直接获取到对应的ivar,method,property,这样在下次访问到的时候就不用再去找
- 如果没有获取到,就开始创建一个YYClassInfo获得class中的描述信息
2.2 创建YYClassInfo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
- (instancetype)initWithClass:(Class)cls { // 判断cls的合法性 if (!cls) return nil; self = [super init]; // cls _cls = cls; // superClass _superCls = class_getSuperclass(cls); // 判断是否为meta _isMeta = class_isMetaClass(cls); if (!_isMeta) { _metaCls = objc_getMetaClass(class_getName(cls)); } // 获得类名 _name = NSStringFromClass(cls); [self _update]; // 获得_superInfo _superClassInfo = [self.class classInfoWithClass:_superCls]; return self; } |
这里调用到了_update更新信息的接口
2.3 更新
1 2 3 4 |
@implementation YYClassInfo { // 是否需要刷新 BOOL _needUpdate; } |
YYClassInfo维护了一个变量来判断是否需要刷新,通过调用setNeedUpdate来修改_needUpdate=YES,进而调用_update方法来进行刷新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
- (void)_update { // 刷新ivar,method,property信息 _ivarInfos = nil; _methodInfos = nil; _propertyInfos = nil; Class cls = self.cls; // 取得类中的methods unsigned int methodCount = 0; Method *methods = class_copyMethodList(cls, &methodCount); if (methods) { // 创建dictionary来进行缓存method信息 NSMutableDictionary *methodInfos = [NSMutableDictionary new]; _methodInfos = methodInfos; // 遍历methods for (unsigned int i = 0; i |
3. 小结
说到这里关于YYClassInfo的代码已经分析完了,在这个类中,YY对于Ivar,property和Method都进行了一层封装,最后都服务于YYClassInfo,这样做的一个好处就是把原来里层的内容暴露在外层,方便查找,也可以进行缓存,提高访问效率和命中率,这也是对后面进行json转换做的一些工作
4.最后
接下来会对核心类NSObject+YYModel进行研读,一路读下来发现写代码就该像YYModel这样有规范,有匠心精神。最近想研究一下缓存的机制,希望能好好读一下YYCache从中学习。