类的结构(补充)

476 阅读5分钟

前言

上一篇已经探究了类的结构,并得出初步的结论:在类中含有4个成员变量:isasuperclasscachebits,大概如图所示:

未命名文件-5.png

在磁盘上的是这样的:

截屏2021-07-13 下午10.13.22.png

取自WWDC2020,首先有这个对象本身,它包含了最常被访问的信息,指向元类、超类和方法缓存的指针。它还有一个指向更多数据的指针,存储额外信息的地方叫做class_ro_t,如图:

截屏2021-07-13 下午10.12.54.png 包含了类名称Name,方法Methods,协议Protocols和实例变量Ivars等的信息。当类第一次从磁盘加载到内存中时就是这个样子,但是是已经过使用就会产生变化,在了解这个之前需要先了解clean Memorydirty Memory的区别:

  • clean Memory是指加载后不会发生改变的内存,上面的class_ro_t就是clean Memory,因为是只读的。
  • dirty Memory``是指在进程运行时会发生更改的内存的结构一经使用就会变成dirty Memory,因为运行时会向它写入新的数据。 dirty Memoryclean Memory更昂贵,它一直存在于运行的进程中,另外可以移除clean Memory来节省更多的内存空间,在需要的时候可以再从磁盘中加载,macOS可以选择换出dirty Memory,但是iOS不使用swap,所以dirty MemoryiOS很珍贵,中的数据被分成2部分就是因为dirty Memoryclean Memory能越多越好,分离出那些不会被改变的数据存储为clean Memory。虽然这样做会在开始运行时的时候要追踪每个更多的信息,所以当一个类首次被使用的时候运行时会分配额外的存储容量,这个额外的存储容量是class_rw_t,用于读取/编写数据。如图:

截屏2021-07-13 下午10.52.34.pngclass_rw_t存储了只有运行时才会生成的新信息。比如所有的都会链接成一个树状结构,这就是通过First SubclassNext Sibling Class指针实现的,这允许运行时遍历当前使用的所有类,这对于方法缓存无效非常有用。但是为什么方法和属性在只读数据中时class_rw_t中还要用方法和属性呢?因为他们可以在运行时进行更改,当分类category被加载的时候,它可以像中添加新的方法,我们也可以通过运行时动态的添加方法,因为class_ro_t是只读的,所以需要在class_rw_t中追踪,但是在实际使用中只用10%的类改变他们的方法,所以就拆掉平时不用的部分,如图:

截屏2021-07-13 下午11.16.19.png 这样就把class_rw_t的大小减少了一半,对于需要修改的就分配这些扩展记录中的一个,并滑到中供使用,如图:

截屏2021-07-13 下午11.21.51.png 这样会再系统范围内节省大概 14MB 的内存。很多从类中获取数据的代码需要同时有扩展数据的类和没扩展数据的类,这些是在运行时处理的,所以外部看起来没有变化,但是内存减少了。

这些就差不多是WWDC2020中关于中的数据存储的东西,我只是搬运了一下。我们通过视频了解了的内存,接下来就要开始具体的探究了。

一、查看ro中的数据

由于上篇文章已经查看过了rw了,这里直接查看ro,我们先看源码。

1、源码分析

我们俩来到objc-runtime-new.h中搜索class_ro_t,找到了如下的结构体:

截屏2021-07-27 下午11.03.57.png 我们注意到baseProtocolsivarsbasePropertiesgetName()baseMethods()这写东西,一会我们将一一验证。

2、lldb验证

ro的结构大概看清楚了,那么我我们从哪里获取到呢?在上篇文章中有提到,在class_data_bits_t中有个safe_ro()方法,接下来我们进行具体的调试。首先创建一个,如图:

截屏2021-07-28 上午12.15.30.png 成员变量和属性都要有,因为有ivarsbaseProperties的区分,调用->断点,如图:

截屏2021-07-28 上午12.20.16.png 然后开始lldb调试,初步如图:

WeChatc44af63be1e93f4e5cfb4c266bee47bf.png baseProtocols为空,我们先跳过。然后ivars,如图:

WechatIMG48.jpeg 这里有5个成员变量。再看baseProperties,如图:

截屏2021-07-28 上午12.44.33.png 这里的属性有4个。getName()调用,如图:

截屏2021-07-28 上午12.47.53.png 最后baseMethods(),如图:

截屏2021-07-28 上午12.53.04.png9个方法,其中8个是属性的的settergetter,选中的方法.cxx_destructC++的析构方法。

3、小结

对比上篇文章中的rw探究,我们不难发现rorw的结构大致一样,这也从一方面验证了前面的dirty Memoryclean Memory

二、类方法

在前面的探索中我们都没有对方法太多关注,现在我们来看一下方法,方法分为类方法和实例方法,那么我们在类中添加这2种方法,如图:

截屏2021-07-29 上午10.33.56.png 为了方便查看我们把成员变量和属性都注销,属性会生成settergetter方法,然后老套路lldb调试,如图:

366E0507-4DAE-46D2-B02A-25DA1C98A089.pngLGPerson的方法列表中我们看到只有一个,打印出来是sayWork的实例方法,再往后取值就直接越界了,我们明明加入了2个方法,类方法sayFunny哪里去了?

  • 猜想:实例方法sayWork的接收者是LGPerson的实例p,类方法sayFunny的接收者是LGPerson,类推LGPerson是谁的实例?在isa的走位图中实例的isa指向的是isa指向的是元类,所以类方法存在元类中? 接下来我们来验证一下,继续lldb

6EB88127-29EA-4923-BD22-97EF43827893.png 在这里找到了sayFunny

小结

实例方法存在中;类方法存在元类中。