【iOS】class的底层结构总结

21 篇文章 0 订阅
12 篇文章 0 订阅

class的结构

方法缓存

我们先来整体看一下结构
在这里插入图片描述

    1. class类中只有isa指针、superClass、cache方法缓存、bits具体的类信息
    1. bits & FAST_DATA_MASK 指向一个新的结构体Class_rw_t, 里面包含着methods方法列表、properties属性列表、protocols协议列表、class_ro_t类的初始化信息 等一些类信息

class_rw_t

class_rw_t里面的methods方法列表、properties属性列表 都是二维数组,是可读可写的,包含 类的初始内容,分类的内容
在这里插入图片描述

class_ro_t

class_ro_t 里面的baseMethodList, baseProtocols, Ivars, baseProperties是一维数组, 是只读的, 包含类的初始化内容

在这里插入图片描述

method_t

method_t是对方法的封装

struct method_t{
  SEL name;    //函数名
	const char *types;  //编码(返回值类型,参数类型)
	IMP imp;    //指向函数的指针(函数地址)
}

IMP

IMP代表函数的具体实现

typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, …);

  • id _Nonnull : 第一个参数是指向self的指针. (如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针)
  • SEL _Nonnull : 第二个参数是方法选择器(selector)
  • 每一个OC方法,都有两个隐藏参数
  1. self参数,指向当前实例的指针,用于访问对象的属性和方法。
    • 在实例方法中,self指向当前对象实例
    • 在类方法中,self指向当前类对象
  2. _cmd参数
    • _cmd是一个SEL类型的参数,表示当前正在执行的方法选择器(方法名)
    • 它可用于在运行时获取的当前方法的名称,比如在日志中输出当前方法的名称。

SEL

SEL代表方法名,一般叫选择器,底层结构跟char *类似

  • 可以通过@selector() 和 sel_registerName()获得
  • 可以通过sel_getName() 和 NSStringFromSelector()专程字符串
  • 不同类中相同名字的方法, 所对应的方法的选择器是相同的
  • 具体实现 typedef struct objc_selector *SEL

types

types包含了函数返回值,参数编码的字符串
结构为:返回值 参数1 参数2 … 参数N
iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码

在这里插入图片描述

例如

// “i24@0:8i16f20”
// 0id 8SEL 16int 20float == 24

  • (int)test:(int)age height:(float)height

每一个方法都有两个默认参数self和_msg,我们可以查到id类型为@, SEL类型为:
- 1. 第一个参数i返回值
- 2. 第二个参数@是id类型的self
- 3. 第三个参数:是SEL类型的_msg
- 4. 第四个参数i 是Int age
- 5. 第五个参数f是float height
其中加载的数字其实是跟所占字节有关的
- 1. 24总共占有多少字节
- 2. @0 是 id类型的self 的起始位置为0
- 3. :8是因为 id类型的self占8字节,所以SEL类型的_msg的起始位置为8

方法缓存

Class内部结构中有一个方法缓存cache_t, 用散列表(哈希表)来缓存曾经调用过的方法, 可以提高方法的查找速度。

在这里插入图片描述

cache_t结构体里面有三个元素
- buckets 散列表, 是一个数组,数组里面的每一个元素就是一个结构体bucket_t , bucket_t 里面存放两个
- _key : SEL方法名作为key
- _imp : 函数的内存地址
- _mask : 散列表的长度
- _occupied : 已经缓存的方法数量

为什么会用到方法缓存

在这里插入图片描述

这张图片是我们方法产找路径,如果我们的一个类有多个父类,需要调用父类方法,他的查找路径为

    1. 先遍历自己所有的方法
    1. 如果在自己类中找不到方法,则遍历父类所有方法,没有查找到调用方法之前, 一直重复该动作。如果每一次方法调用都是走这样的步骤, 对于系统级方法来说,其实还是比较消耗资源的,为了应对这个情况。出现了方法缓存,调用过的方法,都放在缓存列表中,下次查找方法的时候,先在缓存中查找,如果缓存中查找不到,然后在执行上面的方法查找流程。

散列表结构

在这里插入图片描述

散列表的结构大概就像上面那样,数组的下标是通过@selector(方法名)&_mask来求得,具体每一个数组的元素是一个结构体, 里面包含两个元素_imp和@selector(方法名)作为的key

我们在上一篇文章中知道,一个值与&上一个_mask,得出的结果一定小于等于_mask值,而_mask值为数组长度-1,所以任何时候,也不会越界。

其实这就是散列表的算法,也有一些其他的算法,取余,一个值取余和&的效果是相同的。

但是这其实是有几个疑虑的

- 1. 初始 _mask是多少? - 初始_mask为简单尝试了一下,`第一次可能为3`
- 2. 随着方法的增加,方法数量超过_mask值了怎么办?  -    随着方法的增多,方法数量肯定会超过_mask, 这个时候会`清空缓存散列表,然后_mask*2`
- 3.  如果两个值&_mask的值相同了怎么办?  -   如果两个值&_mask的值相同时,第二个&减一,直到找到空值,如果减到0还没有找到空位置,那就放在最大位置
- 4. 在没有存放cach_t的数组位置怎么处理?
    - 在没有占用时,会在空位置的值为NULL

面试题

  1. class_rw_t和class_ro_t结构体中,都有方法列表,属性列表,协议列表,有什么区别呢?

答:

  1. 方法列表:
    - class_ro_t中存储了类的所有静态方法包括从父类继承的方法。这些方法在类的编译时就被确定下来了。
    - class_rw_t中存储了类的动态方法列表,包括在运行时添加或修改的方法。

  2. 属性列表:
    - class_ro_t中存储了类的所有静态属性包括从父类继承的方法。这些属性在类的编译时就被确定下来了。
    - class_rw_t中存储了类的动态属性列表,包括在运行时添加或修改的属性。

  3. 协议列表:
    - class_ro_t中存储了类遵循的所有静态协议,这些协议在类的编译时就被确定下来了。
    - class_rw_t中存储了类在运行时动态遵循的协议列表

  4. 讲一下class的结构,class_rw_t的结构,class_ro_t的结构

在这里插入图片描述

  1. 讲一下这三者的关系

class结构中有一个bits,通过&FAST_DATA_MASK得到class_rw_t结构,class_rw_t结构体中一个成员变量const class_ro_t *ro;

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值