本文由快学吧个人写作,以任何形式转载请表明原文出处。
本节为承上启下的一节,上一节中在探索类的属性的时候,发现了rw,下面的内容中还会出现ro,所以本节主要讲述rw和ro的一些内容。
一、官方信息
苹果官方在3年前的2020年的WWDC大会上更新了一段视频,有关类的数据结构发生了变化。相关视频(需要使用mac自带的Safari浏览器观看)。
- 当类首次从磁盘加载到内存时的结构 :
- 当一个类首次被使用时 :
- 当类在运行时变更了额外的信息时(也就是类被动态修改了,比如例如runtime的API动态的修改类的方法) :
可以看到上面是三种时期,类的三种结构变化,从磁盘加载到内存时,只有MYClass
和class_ro_t
;第一次使用时,有MYClass
->class_rw_t
->class_ro_t
;当类被动态修改时有MYClass
->class_rw_t
->class_rw_ext_t
->class_ro_t
。
所以我们需要了解的概念就包括了图中的Dirty Memory
、Clean Memory
、class_rw_t
、class_rw_ext_t
和class_ro_t
。
二、DirtyMemory和CleanMemory
两个概念很简单 :
DirtyMemory : 俗称脏内存,表示在进程运行的时候,会更改的内存。
CleanMemory : 俗称净内存,表示加载后便不再会发生更改的内存。
为什么要提出DirtyMemory和CleanMemory的结构和概念,因为类只要一经使用就会变成DirtyMemory,比如在运行的时候会向类中写入数据。为了优化内存的使用,所以提出了这个概念和结构。
之所以说优化了内存的使用,是因为DirtyMemory要比CleanMemory的使用成本高出很多,CleanMemory可以在不用的时候移除,节省很多的内存空间,在使用的时候系统会再次用磁盘中加载;而DirtyMemory则不同,只要进程一直在运行,DirtyMemory就会一直存在。
另外,本节要说的class_ro_t
就是CleanMemory,而class_rw_t
和class_rw_ext_t
则是DirtyMemory。rw
被分成了两部分,也是为了尽可能的保持一部分数据的清洁性。通过分离出那些永远不会发生改变的数据,从而优化内存的使用。
三、class_ro_t
1. class_to_t的基本信息
生成时期 : 编译期
ro的意思 : readOnly(只读)
存储的内容 : 类名称、方法、协议、实例变量等信息
ro中是没有分类的方法的,因为分类的方法是在运行时添加的。
源码 : 通过搜索类的本质objc_class :
找到如下(逐步进入红框中的源码) :
只需要看ro的以下常见代码 :
2. 解决上一节遗留的成员变量在类中的存储问题
从ro的源码中可以发现一个ivars
,这就是上一节遗最后留的问题的答案。成员变量是不存储在rw中的,而是存在于ro的ivars里面。
这里解决上一节的遗留问题,直接用上一节的项目,lldb调试图如下 :
到这里之后,看一下class_rw_t的源码,可以找到源码中存在如下函数 :
所以class_rw_t
的实例可以直接调用ro()
函数,获得返回值类型为class_ro_t
的数据 :
得到的$5的类型是class_ro_t
,这就是一个CleanMemory的结构,其内容和上面的class_ro_t
的源码中一一对应。
直接打印出ro中的ivars :
发现新的类型ivar_list_t
,找到源码中的ivar_list_t
,发现也是继承于entsize_list_tt
,所以也有get()
函数,具体原因在上一节中有提到。
那么让ivar_list_t
实例($7)直接调用get()
函数 :
成员变量shenGao
终于找到,另外也证明了属性在编译的时候也会变成成员变量。
三、class_rw_t
生成时期 : 运行时
rw的意思 : readWrite(可读可写)
存储的内容 : 类的方法、协议、属性等信息
class_rw_t
生成的时间是在运行时,详细的生成时间是在runtime运行realizeClassWithoutSwift
函数的时候。
这一步生成了class_rw_t
这个结构体,其中包含了上面刚说过的class_ro_t
,并且会更新data
部分,换成class_rw_t
结构体的地址。
realizeClassWithoutSwift
的源码 :
已经加了注释。
下面就可以放出那两张神图 :
realizeClassWithoutSwift
运行之前,data的数据是指向class_ro_t
的,也就是常说的ro。
realizeClassWithoutSwift
运行之后,data的数据是指向class_rw_t
的,而class_rw_t
的class_ro_t *ro
会指向class_ro_t
。
class_rw_t和class_ro_t的异同点总结 :
- 共同点 : 都存储了类的属性,协议和方法。
- 不同点 :
(1).
class_rw_t
的生成时期是运行时,而class_ro_t
的生成时期是编译期。(2).
class_rw_t
生成的时候会将class_ro_t
的数据剪切过来,然后将类的分类的属性和方法拷贝到其中。总结一句 : 可以说
class_rw_t
是class_ro_t
的超集。并且我们实际访问的时候,访问的属性和方法都是class_rw_t
中的。
四、class_rw_ext_t
生成条件 :
通过
runtime
的API进行动态修改的时候。创建或者生成分类的,并且类和分类都是非懒加载的类(也就说,只有在类中调用了+load方法的时候),因为OC的类大多都是懒加载的类,只有在用到的时候才会将类加载到内存中。
苹果在WWDC2020说了class_rw_ext_t
是可以减少内存消耗的,大约只有10%左右的类需要动态的修改,所以只有大约10%左右的类会生成class_rw_ext_t
。
对于动态修改过的类,可以通过class_rw_t
中的提供的ext()
方法来获取class_rw_ext_t
。
类会动态更新的成员在上面的图中已经展示过了,下面单独拿出来再看一下。
包括了 :方法、属性、协议、DName。
上面解决的上一节的问题,就是一个例子,成员变量在编译时期就确定了,而属性是在运行时还要加载set和get方法,并且会做出一些改变,所以class_rw_t
中拥有属性,而class_ro_t
中拥有成员变量。