七、类的属性的存储

61 阅读4分钟

本文由快学吧个人写作,以任何形式转载请表明原文出处。

此篇为上一节的续写。准备资料和上节一致。

一、思路

  1. 分析类的结构,推断类的属性最可能存储在类结构的哪个成员中。

  2. 获取类结构的内存地址,利用lldb来探查内存中的数据。

二、项目创建

  1. 在源码中创建类、类的属性和实例变量

图片.png

  1. 在main.m中实例化一个对象,然后断点,用来调试。

图片.png

三、查看类的内存情况

  1. 在lldb中输入x/4gx pClass,查看JDPerson类的内存情况。

图片.png

  1. 由上一节中类的结构可以得知,上图中的0x0000000100008198是isa的内存地址,0x000000010036a140是superClass也就是父类的内存地址,这里不再验证。

  2. 类这个结构体有四个成员,isa,superClass,cache,bits。isa和superClass已经确定其内存位置并且知道它们不会存储类的属性和实例变量。那么最有可能的存储位置就是cache和bits中,而cache是缓存的意思,bits是数据的意思,所以推断bits最可能存储。那么重点就是如何获取bits的内存地址。

  3. 获取bits的内存内容,就需要知道它的地址。需要进行内存平移。我们可以知道isa是占8字节的内存,superClass也是占8字节的内存,但是不知道cache是什么结构,占用多少的内存,所以要获取cache所占用的内存大小。那么就需要在源码中查看cache的数据结构。在源码中找objc_class :,找到源码。

图片.png

  1. cache的类型是cache_t,查看cache_t的源码。

图片.png

6.cache_t有两个私有成员,一个是8字节的_bucketsAndMaybeMask,大小很容易知道,前面的uintptr_t告诉了成员的类型。还有一个是联合体,联合体内是一个结构体和一个指针类型,指针类型大小是8,结构体的大小看成员,看mask_t的大小。

图片.png

  1. 因为我们真机都是64位系统,所以mask_t的大小就是32位,也就是4字节。再看5中的图,联合体中的结构体还有一个大小是uint16_t,就占2字节,也就是说,无论如何,联合体中的结构体的大小都比指针大小要小,所以联合体的大小就是指针的大小,就是8字节。那么cache_t就是8+8字节,一共16字节。如果看不懂也没事,还有一个简单的方法,在lldb中直接输出cache_t类型的大小。

图片.png

  1. 由上面的4,5,6,7可以得知,类的isa占8字节,superClass占8字节,cache占16字节,所以bits的内存位置就是类的内存起始位置平移8(isa)+8(superClass)+16(cache) = 32字节。得出:

bits的内存位置 = 类的首地址 + 32字节。

  1. 重新运行程序,lldb中执行 : x/4gx pClass,获取类的内存信息,然后将内存的首地址平移32位。

图片.png

四、属性在类中的存储

  1. 上面的得到了bits的内存地址,下面需要将其中的内容读取出来。需要类型强转或者说强定义为class_data_bits_t

图片.png

  1. 这就需要看一下bits的类型class_data_bits_t是什么样的了,所以源码中进入class_data_bits_t的源码。找到和数据相关的一些内容如下 :

图片.png

  1. 有获取data的方法。直接调用data方法。

图片.png

  1. class_rw_t类型的data的数据,直接取指针中的内容。

图片.png

  1. 找不到有关属性的内容,看一下class_rw_t是什么结构的,找到有关属性的代码如下图

图片.png

  1. lldb中让class_rw_t类型的实例($3)去调用这个properties()函数。

图片.png

  1. 得到一个类型为property_array_t的数据。property_array_t的源码就不放了,太长,直接点源码进去看,是个数组,里面还有一个迭代器。直接看它的内容 :list

图片.png

  1. 看ptr。

图片.png

  1. 得到的是一个property_list_t类型的指针,取指针内容看。

图片.png

  1. 得到一个property_list_t类型的实例,不知道如何看其中的内容,所以看它的源码。

图片.png

  1. property_list_t是继承于entsize_list_tt的,本身结构体内没有什么成员和函数,所以要看它的父类entsize_list_tt。

图片.png

  1. 很长,所以没必要全看,找到获取数据的get()方法即可。父类有get,所以property_list_t也有get。那么直接让property_list_t类型($8)的实例调用get。

图片.png

结论 : 看上图,get(0)可以得到自建的属性tiZhong,get(1)直接越界。也就是说类的成员变量shenGao并不在property_list_t中,类的属性tiZhong在class_rw_t的属性列表property_list_t中存在。

五、遗留问题

那么类的实例变量存在于哪里?