OC探究Cache缓存实际运行结果及对源码的理解

一:猜想与运行结果验证

前言:我们知道一个oc对象,在底层都会被编译成一个c++结构体,部分代码如下,这里不再讨论结构体的关系,只列出部分关键源码

struct objc_class;

struct objc_object;

struct objc_object {

private:

    isa_t isa;

}

typedef struct objc_class *Class;

typedef struct objc_object *id;

//注:每个类对象都是该类型的结构体变量,cache就是缓存的方法,bits里存储着该类的所有实例对象方法

struct objc_class : objc_object {

    // Class ISA;

    Class superclass;

    cache_t cache;             // formerly cache pointer and vtable

    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

 

    class_rw_t *data() {

        return bits.data();

    }

//其它声明的函数已做删减

}

 

struct cache_t {

    struct bucket_t *_buckets;

    mask_t _mask;

    mask_t _occupied;

问题引入:但每个对象调用对象方法(这里只探究实例对象方法,类方法和对象方法类似),到底是怎么去查找的?

问题简答:我们每个实例对象调用方法时,分为以下几种情况:

                1.首先去类对象的缓存cache中去查找方法,如果查找到该方法则直接调用

                2.如果在类对象中未找到方法,则去类对象的方法列表寻找方法,如果找到方法,则调用该方法,同时缓存一份到cache中

                3.如果在类对象的cache和方法列表中都没有找到该方法,则通过类对象的superClass指针到父类的类对象的cache中查              

                    找,如果找到,则调用该方法,同时缓存一份到自身的类对象的cache中

                4.如果在自身的类对象的cache中,方法列表中,父类的cache中都没找到,则到父类的方法列表中查找,如果找到,则调用该

                    方法,同时缓存一 份到父类类对象的cache中,也缓存一份到自己类对象的cache中.

                5.如果在父类的方法列表里也找不到该方法,则重复执行4,层层向上查找,直到找到NSObject,如果NSObject都没有,那

                   查找过程就结束,报错

验证:我们声明两个类,继承关系:EZStudent-->EZPerson-->NSObject

         注意:查看缓存的方法,首先我们需要拿到类对象的cache缓存,打印缓存列表即可.因为OC对象在底层都会被编译成结构体,

                 所以我们自己构造和底层类型一模一样的结构体,然后强制转换为我们的结构体类型的变量,就能打印出实际运行过程

                 中每个结构体成员的值.我们这里的目的是拿到objc_class结构体变量中的cache成员.

 

struct my_buckets_t{

    my_cache_key_t _key;

    IMP _imp;

};

//cache在底层也是个结构体

struct my_cache_t{

    struct my_buckets_t *_buckets;//缓存的方法数组

    my_mask_t _mask;//散列表长度减一,和SEL做相与操作可得到散列表索引值,方知该方法缓存到散列表的哪个位置.

    my_mask_t _occupied;    //已缓存的方法数量

//用来获取缓存方法的key,imp等值

    IMP imp(SEL selector){

        my_mask_t begin = _mask & (long long)selector;

        my_mask_t i = begin;

        do {

            if (_buckets[i]._key == 0  ||  _buckets[i]._key == (long long)selector) {

                return _buckets[i]._imp;

            }

        } while ((i = cache_next(i, _mask)) != begin);

        return NULL;

    }

    

};

 

struct my_objc_object{

    Class isa;

};

struct my_objc_class : my_objc_object{

    Class superclass;    

   struct my_cache_t cache;    

...//其它信息省略,因为这里不研究其它成员的信息

};

                   

 

 

 

从结果可以看出我们上面的猜想.

 

另外,散列表会根据实际情况,进行扩容.按源码的规定,当当前即将调用增加缓存的方法时,如果超过散列表长度的3/4,就会扩容,扩容是将当前散列表长度进行*2,但有上限,超过上限不允许再扩容,初始散列表长度为4(这些规定在源码中都能找到,下面我们会一一解读源码),现在看实际运行结果,当我们调用了init,studentRun

从上图可以看出,init方法的key值为100509109,转成十六进制就是5fda5b5,和值为3的mask相与结果为1,即得到散列表的索引值.最终key为100509109,地址为0x101bcee2f名为init的方法就缓存在散列表索引为1的位置里.

 

从运行结果看,也符合我们的猜想.下面就是从源码角度来再次印证猜想.

 

 

 

 

二.源码解读

1.解读思路:

 需要了解的几点:源码从哪里开始看?看哪些方法?

a>我们需要解读cache的源码,就要知道cache是存在哪里的,我们知道cache是objc_class结构体的一个成员.首先就得看objc_class结构体,如图:

b>要研究cache就得进cache查看cache到底是什么,如图:

其中typedef uintptr_t  cache_key_t;

这里有一点要说的是,Objective-C 数据结构中,存在一个 name - selector 的映射表如图:
12(方法对应的key)   -->addObject:
755                            -->setEntryDate
332                            -->count

c>从a和b两个步骤中我们已经看到cache,散列表buckets,需要缓存的方法地址imp,需要缓存的方法的key的对应关系.现在就看程  

    序运行过程中方法的调用顺序.因为实例对象调用实例对象方法是从类对象的cache中查找方法,如果查找不到,则到类对象的方

   法列表里继续查找.会进入cache_fill_nolock()函数开始查找方法,根据查找结果做相应处理.现在看看这个函数的实现:

截图中注释已经很详细了,就不再说了,现在需要查看:第一点:expand()函数是怎么对散列表进行扩容的,第二点:find()函数是怎么在散列表中寻找到合适位置存放本次调用的方法的,第三点:set()函数是怎样将方法地址imp和key存到cache里的

d>首先看expand()函数,代码截图和注释如下:

其中的INIT_CACHE_SIZE源码截图如下:

e>现在再来看find()函数是怎么查找到合适的空位,用来存储新的方法的,代码截图和注释如下:

f>继续看set()函数的细节,代码截图和注释如下:

     在这之前,有一句代码

if (bucket->key() == 0) cache->incrementOccupied();

    bucket->set(key, imp);

表示,如果当前位置的key为0,这个位置就没有存储过方法,所以要对结构体的occupied成员加1操作,如果不为0,则表示散列表存储过这个方法,set函数会对已经存储过的方法做单独处理

g>reallocate函数细节,初始化散列表和散列表扩容时都会调用该函数,代码截图注释如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值