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函数细节,初始化散列表和散列表扩容时都会调用该函数,代码截图注释如下:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 使用MEME进行motif分析 MEME是一种常用的motif分析工具,可以用于从序列数据中识别出潜在的motif。利用dap-seq输出的peak序列,可以使用MEME进行motif分析。 首先,需要将dap-seq输出的peak序列转换为fasta格式的文件。可以使用bedtools将peak序列提取出来,并将其转换为fasta格式: ``` bedtools getfasta -fi genome.fa -bed peaks.bed -fo peaks.fa ``` 其中,genome.fa为参考基因组序列文件,peaks.bed为dap-seq输出的peak文件,peaks.fa为输出的fasta格式的peak序列文件。 然后,可以使用MEME对peaks.fa进行motif分析: ``` meme peaks.fa -oc meme_output -nmotifs 10 -minw 6 -maxw 20 ``` 其中,meme_output为输出结果的文件夹,-nmotifs指定需要识别的motif数量,-minw和-maxw分别指定motif的最小和最大长度。 2. 使用Homer进行motif分析 Homer是另一个常用的motif分析工具,也可以用于从序列数据中识别出潜在的motif。类似地,利用dap-seq输出的peak序列,可以使用Homer进行motif分析。 首先,需要将dap-seq输出的peak序列转换为bed格式的文件。可以使用bedtools将peak序列提取出来,并将其转换为bed格式: ``` bedtools sort -i peaks.bed > sorted_peaks.bed bedtools merge -i sorted_peaks.bed > merged_peaks.bed awk 'BEGIN{OFS="\t"}{print $1,$2,$3,"peak_"NR,".",$6}' merged_peaks.bed > peaks_homer.bed ``` 其中,peaks.bed为dap-seq输出的peak文件,sorted_peaks.bed和merged_peaks.bed为中间文件,peaks_homer.bed为转换后的bed格式的peak文件。 然后,可以使用Homer对peaks_homer.bed进行motif分析: ``` findMotifsGenome.pl peaks_homer.bed genome_dir homer_output -size 200 -p 8 ``` 其中,genome_dir为参考基因组序列文件夹,homer_output为输出结果的文件夹,-size指定motif的长度,-p指定使用的线程数。 3. 对比分析motif 对于使用不同的工具进行motif分析得到的结果,可以使用Tomtom进行对比分析。Tomtom是一个用于motif比对和聚类的工具,可以帮助用户在已知的motif数据库中搜索相似的motif,并将它们聚类为同一个motif家族。 首先,需要将使用不同工具得到的motif结果转换为meme格式的文件,并将其放入同一个文件夹中,如motif_dir。 然后,可以使用Tomtom进行对比分析: ``` tomtom -o tomtom_output -verbosity 1 -thresh 0.1 -eps -text -min-overlap 5 -dist pearson -no-ssc motif_dir/motif1.meme motif_dir/motif2.meme ``` 其中,tomtom_output为输出结果的文件夹,-thresh指定使用的阈值,-dist指定使用的距离度量方式,-no-ssc表示不使用自身比对,motif1.meme和motif2.meme为需要比对的motif文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值