iOS底层探索 --- 类的结构探索(上)

2021-06-25  本文已影响0人  Jax_YD
image

今天我们将进行类的结构体的探索,其中有些内容我们在iOS底层探索 ---Runtime(一)--- 基础知识有做过探索。不过没关系,今天我们再来回顾一下。

本章我们将探索一下内容:

1、类的结构(类,元类,根类之间的关系)

2、objc_object & objc_class

3、类中信息的探索


1、类的结构(类,元类,根类之间的关系)

1.1 类(Class)

我们都知道,我们创建的基本上都继承自NSObject,而NSObject里面有一个默认参数isa;也就是说,继承自NSObject里面都会有一个isa。如下:

image

假设我们现在有一个Person类,那么Person对象的首地址就是isa地址,同样的isa也代表当前的类:

image

1.2 元类(Meta Class)

这个时候,我们如果继续查看类地址,会打印什么呢?下面我们就来尝试一下:

image
可以看到,继续打印类地址,输出的是类的内存分布
我们还可以通过x Person.class来查看类的内存分布 image image

大家可以看到,此时我们同样打印出了Person。这就奇怪了!!!

1、Meta Class(元类)就是类对象所属的。一个对象所属的类叫做类对象,而一个类对象所属的类就叫做元类

2、Meta Class(元类)的定义和创建,都是由编译器自动完成。

3、所有的类方法,都存储在Meta Class(元类)中。


1.3 根类

在上面我们已经找到了Meta Class (元类),那我们继续往下去寻找又会有什么发现呢?

image

我们会发现,居然指向了NSObject

此时我们我们如果执行p/x NSObject.class这句指令,打印出来的NSObject地址会是什么呢?

image

此时有没有发现问题,NSObject的地址不一样呀。这是怎么回事呢?

这是因为p/x NSObject.class中打印的NSObject并不是根类,而是根元类。怎么证明呢?我们只需要打印一下NSObject.class的内存信息,就会看到起isa上面打印的NSObject的地址是一样的。

换句话说,就是元类isa 指向了根元类

(注:严谨一点是要& ISA_MASK,这里由于isa中只有shifcls信息,可以直接看出来)。执行结果如下:

image

此时就回到我们在iOS底层探索 ---Runtime(一)--- 基础知识中探索过的那幅图:

[图片上传失败...(image-caaf2f-1624499873237)]


1.4 类对象在内存中的个数

类对象在内存中,有且仅有一份!!!
这也是一道经常被问到的面试题,下面我们来证明一下:
我们创建多个类对象,然后打印,看一下地址是否相同

image

通过打印可以发现,虽然创建了多个类对象,但是地址都是一样的。因此我们可以说:类对象在内存中,有且仅有一份。


1.5 类的继承关系的坑点


2 objc_object & objc_class

大家还记不记得,我们在iOS底层探索 --- OC对象原理(下)中,在我们转换mian.cpp文件中有这样一段代码:

image

当时我们只是对objc_classobjc_object简单的讲解了一下。那么它们两个具体是什么样子的,又有什么样的关系呢?今天我们来详细的探索一下。

2.1 源码寻找

首先我们要在源码中去寻找objc_objectobjc_class到底长什么样子。

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

   class_rw_t *data() const {
       return bits.data();
   }
   void setData(class_rw_t *newData) {
       bits.setData(newData);
   }

   ......
   ......
   ......
}
image image

通过main.cpp文件,我们可以确定,使用的是objc.h里面的objc_object(看注释,这个是给类的实例对象用的)。


2.2 objc_classobjc_object之间的关系

  1. 结构体objc_class继承自结构体objc_object

  2. objc_object有一个isa属性,所以objc_class也有一个isa。(在新的objc_class中有一段注释// Class ISA;,也从侧面表现了这一点。)

  3. NSObject也拥有isa。因为NSObject在底层的表现形式就是objc_object。或者说,所有的NSObject对象,都拥有isa

大家在捋这层关系的时候,要注意objc_object在源码中有两个定义,使用的范围也不一样。不要搞混了,不然会陷入死循环。

注意:objc_class继承的是objc-privat.h里面的objc_object


3 类中信息的探索

在日常开发过程中,我们定义一个类的时候,不仅仅是只有isa,还会有成员变量方法等等一些信息。下面我们我们就来看一下这些信息在哪里。

3.1 内存平移,获取bits信息

在探索类里面信息之前,我们要先达成一个共识,内存的查找是内存平移来获得的。也就是说objc_class里面的变量,是通过内存平移来获得的。举个例子如下:

image image

objc_class中,isa8字节superclass也为8字节;我们不知道的是cache的内存大小。那么关键的就是获取cache的内存大小。

下面我们就来探索一下cache的大小,首先进入cache_t:

image
发现代码有很长,这个时候要怎么确定cache的大小呢?

注意:大家不要看到cache_t是一个结构体,就觉得cache8字节

结构体指针是8字节

结构体的大小要根据其内部的属性来计算。

计算的时候只计算属性,方法不算,静态的属性也不计算

通过上面我们就可以确定,cache内存大小的计算,只需要计算其中四个属性的大小(因为有if判断,大家可以仔细阅读以下这段源码。):

image
image

整体算下来之后,cache的内存大小为8 + 4 + 2 + 2 = 16

那么bits的内存地址,就是首地址平移32个字节8 + 8 + 16

接下来我们就可以通过控制台打印一下bits信息了:

image

到这里,我们已经拿到了bits里面的信息了;但是我们并没有看到我们想要的成员变量方法等等的信息。

不要灰心,下面我们继续探索。


3.2 继续探索类信息

3.2.1 method(方法)

上面我们知道bits的类型是class_data_bits_t,那么我们就跟进去找一下data

image

继续跟进class_rw_t(这个里有个小技巧,我们可以先找method相关的内容,其他看不懂的内容可以忽略),我们发现了这个:

image

这就是我们要找的内容呀,方法是存放在方法列表里面的,而这里正好是一个array

这个时候,我们继续在控制台操作:

image
我们给Person添加几个方法,再来查看一下: image

)

重复上面的步骤,此时可以看到methods里面有信息了:

image

接下来就是去找我们的Method的了,这个控制台流程如下:

image

这里面有一个big()方法,是因为在objc4-818.2的版本中,method_list_t里面的元素是method_t,而method_t被重写了。

新的method_t长这个样子,我们要拿到方法名,就要通过big

image
method_t里面正好有对应的big()方法:
image
3.2.2 property(属性)
image

这个跟探索method前期是一样的操作,只不过后面获取property的时候略有不同;因为property_t直接就有name相关的属性:

image

整个控制台的操作如下:


image
3.2.3 会遇到的一些问题
image

既然属性列表里面没有成员变量的信息,那我们就需要继续寻找了。

同样的bits->data()的返回类型是class_rw_t(注:上面已知);那我们就在class_rw_t里面找。(正常来讲,优先查找methods()附近的方法)

查找过之后,我们会发现,成员变量ivar在这里:

image
image

下面就看我们能不将成员变量打印出来了,打印流程如下:

image

通过上图我们发现,最后怎么没办法执行get方法了呢?
没关系,$7没法用get,那我们就使用$6:

image

最后成功打印成员变量jaxName

这个时候,我们既要去元类中寻找;同样的根据我们查找当前类method_lidt_t的经验,我们在元类中也这样查找。查找流程如下:

image
上一篇下一篇

猜你喜欢

热点阅读