美文网首页
Runtime之Class结构

Runtime之Class结构

作者: coder_feng | 来源:发表于2019-06-16 14:53 被阅读0次

    通过之前一篇文章Runtime之isa,估计大家对isa的本质都比较了解,现在这篇我们来看一下Class结构的相关内容。

    Class 类结构

    从源码中我们可以看到Class的结构大概如下:

    class主要图 bits.data()

    class_rw_t

    class_rw_t

    class_rw_t 里面的methods,properties,protocols是二维数组,是可读可写的,因此可以动态的添加方法,并且更便于分类方法的添加,包含了类的初始化内容,分类的内容,并且类在relizeClass的时候,将class_ro_t的类初始化信息和分类信息(attachList函数内通过memmove和memcpy两个操作将分类的方法列表合并在本类的方法列表中)组合成class_rw_t类型

    rezlizeClass

    从上述源码中就可以发现,类的初始信息本来就是存储在class_ro_t中的,并且ro本来是指向cls->data()的,也就是bits.data()得到的是ro,但是在运行的过程中创建了class_rw_t,并将cls->data 指向rw,同时将初始化信息ro赋值给rw中的ro,最后在通过setData(rw)设置data,那么此时bits.data()得到的就是rw,之后再去检查是否有分类,同时将分类的方法,属性,协议列表整合存储在class_rw_t的方法,属性及协议列表中

    class_ro_t

    class_ro_t

    class_ro_t 里面的methodList,baseProtocols,ivars,baseProperties是一维数组,是只读的,包含了类的初始内容

    method_t

    我们知道method_array_t、property_array_t、protocol_array_t中以method_array_t为例,method_array_t中最终存储的是method_t,method_t是对方法、函数的封装,每一个方法对象就是一个method_t。通过源码看一下method_t的结构体

    method_t type Encoding

    mehod_t 是对方法的封装,IMP代表函数的具体实现,SEL代表方法函数名,一般叫做选择器,可以通过@selector(),和sel_registerName()获得,可以通过sel_getName()和NSStringFromSelector()转成字符串,types包含了函数返回值,参数编码的字符串。

    IMP

    debug mode imp 是否指向函数实现

    type

    type


    根据前面type Encoding 图,可以知道这个代表

    v:代表返回值void 

    16 表示参数的占用空间大小

    @:id

    0:id后面的0表示从0位开始存储,id 占8位空间

    :代表SEL

    8:表示从第8位开始存储,SEL同样占8位空间

    - (void)personTest;我们知道任何方法都默认有两个参数的,id类型的self,和SEL类型的_cmd,而上述通过对types的分析同时也验证了这个说法。现在我们添加参数再来测试相关情况看看

    - (void)personTest:(int)age height:(float)height:

    v24@0:8i16f20

    v        24    @        0        :    8    i    16    f            20
    void            id               SEL        int         float

    参数的总占用空间为8 + 8 + 4 + 4 = 24

    id 从第0位开始占据8位空间,SEL从第8位开始占据8位空间,int 从第16位开始占据4位空间,float从20位开始占据4位空间

    另外iOS提供了@encode的指令,可以将具体的类型转化成字符串编码

    @encode

    SEL

    SEL可以通过@selector()和sel_registerName()获得

    @selector()和sel_registerName()

    也可以通过sel_getName()和NSStringFromSelector()将SEL转成字符串

    sel_getName()和NSStringFromSelector()

    不同类中相同名字的方法,所对应的方法选择器是相同的。关于这点,你可以自己打印看看的

    NSLog(@"%p,%p", sel1,sel2); 会发现结果是一样的

    方法缓存cache_t

    cache_t

    buckets:散列表;

     _mask:散列表的长度 - 1;

    _occupies:已经缓存的方法数量

    还没有缓存personTest 缓存了peronTest bucket_t

    _key:SEL作为key;_imp:函数的内存地址

    其实cache_t cache 是用来缓存曾经用过的方法,可以提高方法的查找速度,前面介绍OC对象的时候,已经说过了,如果一个对象调用方法的时候,需要去方法列表里面进行遍历查找,如果方法列表不存在,就会通过superclass找到父类的类对象,在去父类的类对象方法列表里面查找,一直找到顶层,但是如果方法需要调用很多次的话,那就相当于每次调用都需要去遍历很多方法,这样对性能是有一定损耗的,特别是如果要寻找的方法都是在父类里面的话,整个查找效率就会显得低下了,所以就要有cache_t来进行方法缓存,每当调用方法的时候,会先去cache中查找是否有缓存的方法,如果没有缓存,再去类对象方法列表中查找,以此类推直到找到方法之后,就会将方法直接存储在cache中,下一次在调用这个方法的时候,就会在类对象的cache里面找到这个方法,直接调用了,不会再先之前描述的那样又要重新执行一遍流程


    cache_t 缓存原理

    从上述的源码中,可以看到bucket列表是一个散列表,也叫哈希表,是根据关键码值(Key Value)而直接进行访问的数据结构,也就是说,它通过关键码值映射到表中一个位置来访问记录,以加快查找的速度,这个映射函数叫做散列函数,存放记录的数组叫做散列表。苹果又是怎么设计这个逻辑的呢,通过什么函数能够在列表中快速并且准确的找到对应的key以及函数实现呢?

    缓存主要方法

    cache_fill及cache_fill_nolock 函数

    cache_fill cache_fill_nolock

    reallocate函数

    reallocate 扩展容量的枚举

    expand()函数

    当散列表的空间被占用超过3/4的时候,散列表会调用expand()函数进行扩展,看看一下expand()函数内散列表如何进行扩展的

    expand

    find 函数

    find函数是通过散列函数,找到对应的位置,然后找到对应的value,看源码图:

    find cache_hash cache_next

    cache 总结

    当第一次使用方法时,根据运行时的消息机制同isa找到对应的方法之后,会对方法以SEL为key,IMP为value的方式缓存在cache的_buckets中,当第一次存储的时候,会创建具有4个空间的散列表,并将_mask的值赋值为散列表长度减1,然后将SEL&mask计算出来的方法存储的下标值,并将方法存储在散列表中。举例说明,如果计算出的下标值为3,那么就直接将方法直接存储在下标为3的空间中,前面的空间会留空,这个也是cache缓存快的原因,通过空间换时间,另外当散列表中存储的方法占据散列表长度超过3/4的时候,散列表会进行扩容操作,将创建一个新的散列表并且空间扩容至原来空间的两倍,并重置_mask的值,最后释放旧的散列表,此时再有方法需要进行缓存的话,需要重新通过SEL & mask计算出,然后再存储新的,旧的会全部释放掉;另外如果一个类中方法很多,其中很可能会SEL & mask得到的值为同一个下标值,那么会调用cache_next 函数往下减1,然后重新判断,如果下标值-1 位空间有存储方法,并且key没有与存储的key相同,那么继续再到前面一位进行比较,直到找到一位空间没有存储方法key等于0的,或者key与要存储的key相同为止,如果到下标为0的话,就会到下标为_mask的空间重新再查找,可以看看下面两张图:

    散列表逻辑存储图

    当方法loadData需要缓存的时候,这个时候通过SEL&_mask的值为2,但是此时获取的_buckets[2].key() = studentTest,没有等于0,也没有等于loadData,这个时候会调用cache_next函数重新进行下一次判断,然后到了_buckets[1].key() = 0,因此@selector(loadData)存放在下标为1的空间内。

    测试验证环节

    这一步,就留给感兴趣的人,自己写代码自己验证哈,如果理解前面所的内容,自己验证应该不难,也可以参考demo来验证,enjoy!

    相关文章

      网友评论

          本文标题:Runtime之Class结构

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