一、class_rw_t 数据结构
Method 相关结构体以及存贮结构二、method_t 数据结构
method_t- 和其他编程语言一样,一个方法我们需要保存它的
函数名
、返回值
、参数
、函数地址
1. SEL
-
SEL 代表
方法名/函数名
,一般叫做选择器
,底层结构跟char*
类似,我们可以把它看成方法名字符串
-
可以通过
@selector()
和sel_registerName()
获得 -
可以通过
sel_getName()
和NSStringFromSelector()
转成字符串 -
不同类中相同名字的方法,所对应的方法选择器是相同的
SEL sel1 = @selector(setTest:);
SEL sel2 = sel_registerName("setTest:");
// 打印日志
(lldb) p/x sel1
(SEL) $0 = 0x00007fff3a201718 "setTest:"
(lldb) p/x sel2
(SEL) $1 = 0x00007fff3a201718 "setTest:"
- 换句话说:名字相同的方法,内存中只会保存一份
SEL
2. IMP
注意图中红框位置- 由此我们可以得出
IMP
所存放的就是函数的地址
3. Types
-
types
采用了类型编码技术(TypeEncodings
),存贮方法的返回值类型
和参数类型
-
简单来说就是将各种
类型
映射成各种符号
,这个对照表苹果官网有,类似于如下:
- 接下来,我们拿一个具体方法解析一下
三、cache_t 方法缓存
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
}
图解整个过程我们之前学过,当我们调用一个实例方法时,会先从
isa
指针入手,查找类对象的方法列表
,然后使用superclass
逐级查询父类的类对象方法列表
。而我们现实代码中,通常来说都有一些常用的方法,如果每次都要走上面的步骤去查找,效率就会显得比较低。所以苹果设计了方法缓存机制
,让方法缓存在cache
中。
有如下代码结构:
-
Person
继承自NSObject
,有- (void)personTest;
方法 -
Student
继承自Person
,有- (void)studentTest;
方法 -
GoodStudent
继承自Student
,有- (void)goodStudentTest;
方法
1. 当我们调用 GoodStudent
的 - (void)personTest;
方法,该方法会被缓存到哪里?
int main(int argc, const char * argv[]) {
GoodStudent *goodStudent = [[GoodStudent alloc] init];
mj_objc_class *goodSudentClass = (__bridge mj_objc_class *)[GoodStudent class];
mj_objc_class *personClass = (__bridge mj_objc_class *)[Person class];
NSLog(@"断点1");
[goodStudent personTest];
NSLog(@"断点2");
return 0;
}
-
断点1
,我们观察goodSudentClass
的cache
属性 获得_occupied = 1
-
断点1
,我们观察personClass
的cache
属性 获得_occupied = 0
-
断点2
,我们观察goodSudentClass
的cache
属性 获得_occupied = 2
-
断点2
,我们观察personClass
的cache
属性 获得_occupied = 0
-
为什么
- (void)personTest;
方法未调用时候,goodSudentClass
的cache
属性的_occupied = 1
呢?别忘记了-(id)init;
方法哟 -
由此,我们可以得出
子类
调用父类的方法
时,父类方法
会被缓存到子类类对象
的方法缓存列表
中
2. 上面提到方法缓存列表
是一个散列表,如何证明,OC 内部使用的散列算法是 @selctor(funName) & mask
这种算法呢?方法一:
从源码中寻找
源码中缓存列表查找过程
3. 上面提到方法缓存列表
是一个散列表,如何证明,OC 内部使用的散列算法是 @selctor(funName) & mask
这种算法呢?方法二:
从代码中证明
- 我们编写如下代码
int main(int argc, const char * argv[]) {
GoodStudent *goodStudent = [[GoodStudent alloc] init];
mj_objc_class *goodSudentClass = (__bridge mj_objc_class *)[GoodStudent class];
[goodStudent studentTest];
[goodStudent personTest];
int begin1 = (unsigned long)@selector(studentTest) & 3;
int begin2 = (unsigned long)@selector(init) & 3;
int begin3 = (unsigned long)@selector(personTest) & 3;
NSLog(@"studentTest-index:%d",begin1);
NSLog(@"init-index:%d",begin2);
NSLog(@"personTest-index:%d",begin3);
bucket_t *buckets = goodSudentClass->cache._buckets;
for (int i = 0; i <= goodSudentClass->cache._mask ; i++) {
bucket_t bucket = *(buckets+i);
printf("索引值:%d , 方法名:%s , 方法地址:%p \n",i,bucket._key, bucket._imp);
}
NSLog(@"断点1");
return 0;
}
- 输出日志如下(真机运行 arm64):
Demo[3887:171505] studentTest-index:3
Demo[3887:171505] init-index:1
Demo[3887:171505] personTest-index:3
索引值:0 , 方法名:(null) , 方法地址:0x0
索引值:1 , 方法名:init , 方法地址:0x1055891ad
索引值:2 , 方法名:personTest , 方法地址:0x104c575d0
索引值:3 , 方法名:studentTest , 方法地址:0x104c575a0
-
我们发现
-(id)int
和-(void)studentTest
方法的索引值和我们计算出来的一致 -
然而
-(void)personTest
方法因为也是3
,这就发生了重复,这种重复我们叫做哈希碰撞
-
所以
-(void)personTest
需要另外找个位置存贮,从上述源码分析中,我们发现return i ? i-1 : mask;
充分说明会往上找位置,所以-(void)personTest
被存贮在了索引为2
的位置。这一点,我们从代码中也验证成功_
4. 如果方法缓存列表
满了会怎么做呢?
方法缓存散列表扩容示意图
- 当缓存占用的长度是总长度的 3/4 时,会进行扩容
- 新缓存列表长度是旧的两倍
- 旧的列表会被释放,不会覆盖到新的列表
网友评论