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里面确实存在着他的父类的额方法
打印缓存方法
网友评论