Runtime-方法

01class的结构

元类对象和元类的结构是一样的,他是一种特殊的类对象

struct objc_class : objc_object {
    // Class ISA;
    isa_t isa;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable方法缓存
    class_data_bits_t bits; //用来获取具体的类信息
}
类的内存结构

class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容

class_rw_t的内容

class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容,类当初我们声明的方法和属性协议,

class_ro_t里面的结构

方法02-method

method_t就是对函数/方法的封装,他的源码封装为

struct method_t {
    SEL name; //函数名 选择器 他就是一个名字。
    const char *types; //编码(返回值类型参数类型)
    MethodListIMP imp;//指向函数的指针(函数地址)
}
  • IMP代表函数的具体实现

    IMP
  • SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似
    可以通过@selector()和sel_registerName()获得
    可以通过sel_getName()和NSStringFromSelector()转成字符串
    不同类中相同名字的方法,所对应的方法选择器是相同的
typedef struct objc_selector *SEL;
  • types包含了函数返回值、参数编码的字符串

    types

方法03-Type%20Encoding

- (void)test;

通过转成源码的结构体类型,打印出方法的types是里面是v16@0:8
v=void返回值,16所有参数的字节数@第一个参数(self)0开始位子;:第二个参数IMP,8参数从第8个字节开始,每个方法都有这两个默认的参数

  • iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码
NSLog(@"%s",@encode(int));//i
    NSLog(@"%s",@encode(SEL));//:
    NSLog(@"%s",@encode(id));//@
    NSLog(@"%s",@encode(void));//v
类型转成字符1
类型转成字符2

04Cache_t

在objc_class结构体的里有个Cache_t 用来做方法缓存的

  • Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度。去缓存数组找的时候,他会先判断一下key是不是相同找的selector,如果相同就调用

因为假如一个方法要调用很多次的话,每次都进行isa遍历去查找,会很麻烦,性能比较低,所以,第一次调用的时候,把这个方法放到这个里面,不管这个方法之前在哪里都会给缓存到类对象的cache里面,方法缓存,下次通过isa找到类对象的时,调用的时候先到这个cache里面查找,候,有没有那个方法,没有的话再去按照之前的方式分层查找。

cache_t结构

key = @selector(personTest);
imp = personTest的地址

05散列表缓存方法

分析他去缓存中找的时候是怎么快速找到的,散列表里面装的也是数组一样的元素

//他是拿到key& _mask
@selector(studentTest)  & _mask = 4生成就是索引,这个值在他当初把方法放到缓存的时候就已经做好了。在往里面的放的时候,就是按照这个计算结果,将方法放到对应的位子,假设数组一开始为空的,那么他前面就是空的没有东西

@selector(studentTest2)  & _mask = 2,他就会把它放到2纳那个位子,牺牲内存空间,来提升访问速度,因为可能会产生空的位子
,左边的值无论怎么变都<=右边的mask
如果两个方法的地址值生成的索引是一样的。
他寸纯的时候就会将这个值-1;
取的时候如果发现key和想要的不一样,那就-1;
如果到0,还没有,他就变成mask再一次次-1;
假设数组的长度不够了,他就会扩容,一旦扩容,他会将缓存清空

哈希表,就是用一个函数根据传入的key,计算出一个索引,如果索引冲突了,再进某种操作-1+1或者其他来解决这个冲突

散列表

06查看方法缓存

平时调用过的方法,就会放到cache之中,这样做的好处就是下次再调用这个方法的时候可以更快捷的找到他。就算方法是在父类的类对象里面找到也会缓存到自己类对象的cache里面。
让我们测试的类转成自己写的结构体,

07查看方法缓存

通过调用他父类的以及父类的父类的方法,然后查看结构体的cache,可以发现她也会将那些方法也放到缓存之后

GoodStudent *person = [[GoodStudent alloc] init];

        sl_objc_class *personClass = (__bridge struct sl_objc_class *)[GoodStudent class];
        
        [person goodStudentTest];
        [person studentTest];
        [person personTest];
        [person goodStudentTest];
        NSLog(@"-------------------");
        
        cache_t cache = personClass->cache;
//        bucket_t *buckets = cache._buckets;
//
//        bucket_t bucket = buckets[(long long)@selector(studentTest) & cache._mask];
//        NSLog(@"%s %p", bucket._key, bucket._imp);
//
//        for (int i = 0; i <= cache._mask; i++) {
//            bucket_t bucket = buckets[i];
//            NSLog(@"%s %p", bucket._key, bucket._imp);
//        }
        
        NSLog(@"%s %p", @selector(personTest), cache.imp(@selector(personTest)));
        NSLog(@"%s %p", @selector(studentTest), cache.imp(@selector(studentTest)));
        NSLog(@"%s %p", @selector(goodStudentTest), cache.imp(@selector(goodStudentTest)));

cache里面确实存在着他的父类的额方法

打印缓存方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值