一、原理
cache_t:结构体,相当于缓存,缓存的是方法, 它要根据自身结构体所容纳的空间大小来决定其所占字节数。(另外,cache_t是结构体,不是结构体指针。Class为结构体指针,所占字节为8)
struct cache_t {
struct bucket_t *_buckets; // 8
mask_t _mask; // 4
mask_t _occupied; // 4
通过观察bucket_t可以得出缓存的是一些方法
![](https://img.haomeiwen.com/i1398418/586d1999f5e6db36.png)
cache_t缓存的是一些方法:
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
通过以下几个步骤可得出新结论:
![](https://img.haomeiwen.com/i1398418/eba6fa9f1b09dcb3.png)
void testInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
NSLog(@"%s",__func__);
}
void testClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy)); // ?
NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
NSLog(@"%s",__func__);
}
跟进 class_getClassMethod
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
继续进 cls->getMeta()
// NOT identical to this->ISA when this is a metaclass
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
结论:类方法的底层还是实例方法
二、cache_t分析产生的问题:
通过跑码分析打印_buckets的值,得出cache_t常用来缓存第一次的数据,如果一次输出没有值,则可二次或者多次打印。
![](https://img.haomeiwen.com/i1398418/3619ba7728d9f94f.png)
此时有缓存,而且_buckets还有返回值
三、cache_t的缓存流程
mask_t mask();
![](https://img.haomeiwen.com/i1398418/dc5dd366a8ff2554.png)
capacity() 其值为1或0,此时还是不知道缓存哪个方法
探究:到底是缓存在哪个方法
——>capacity()——>
![](https://img.haomeiwen.com/i1398418/306122ca0c8727cf.png)
此时要继续在当前页探究查找关于capacity()的调用情况——>expand() 由此衍生出了扩容类,看源码走向可以得出oldCapacity和newCapacity,如果oldCapacity没有,则*2扩容两倍作为新的INIT_CACHE_SIZE
![](https://img.haomeiwen.com/i1398418/019db92a5dc6a96f.png)
继续跟进INIT_CACHE_SIZE,此时看源码1左移两位100=>4,结果应该为4,但是此时的打印结果_occupied的值还是1,而_mask的值却是3,这是为什么呢?
![](https://img.haomeiwen.com/i1398418/1b4df4d49cbe9747.png)
探究:从何时进行扩容
![](https://img.haomeiwen.com/i1398418/62137eceb2af6de1.png)
跟进void cache_t::expand()中的INIT_CACHE_SIZE
enum {
INIT_CACHE_SIZE_LOG2 = 2,
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2)
};
继续在当前页查找expand(),直到找到cache_fill_nolock会发现,此时的expand()是在591行的时候被调用的(调起了方法扩容)
![](https://img.haomeiwen.com/i1398418/2872fe04d1588902.png)
源码流程分析:
570行:cache_fill_nolock:从缓存入口进入
579行:cache_getImp:再从缓存中得到Imp,判断是否有缓存,缓存中如果拿到,则直接返回
581:cache_t *cache = getCache(cls) :如果没有拿到,要从getCache获取原来的结构体cls
582:cache_key_t key = getKey(sel) :如果没有拿到,要从 getKey获取sel
cache_key_t getKey(SEL sel)
{
assert(sel);
return (cache_key_t)sel;
}
585:occupied():读取开辟占用
587:if (cache->isConstantEmptyCache()):
继续分析三大中情况:
1.cache->reallocate
2.bucket_t bucket = cache->find(key, receiver);
3.bucket->set(key, imp);
591:else if (newOccupied <= capacity / 4 * 3) :正常添加bucket
594:cache->expand():如果以上两种都不是,则扩容*
![](https://img.haomeiwen.com/i1398418/09d7c73b05893985.png)
1.oldCapacity*2 :扩容2倍
2.reallocate(oldCapacity, newCapacity);
3.cache_collect(false); 清理内存
![](https://img.haomeiwen.com/i1398418/02d996156a5e4224.png)
整体处理流程大致如下图所示:
![](https://img.haomeiwen.com/i1398418/0431cc4c7a4ce66c.png)
![](https://img.haomeiwen.com/i1398418/e694bd0debcf8405.png)
自我感觉有点糙,还可以再细化,后面有机会,我会给大家添加上一个关于缓存的流程分析图会更加直观和清晰~
网友评论