iOS底层原理03-类,元类,bits(上)

551 阅读8分钟

上篇内容# iOS底层原理02-objc对象的底层探索(下),我们研究了对象的本质,并且通过对象的isa指针找到了类,那么类的本质是什么呢?这期我们来研究一下。

在开始探讨之前,我们先讲一下在学习面向对象开发时的内核:

万物皆对象

那对象时怎么来的呢?我们带着这个问题开始今天的研究。

一:前言 实例-类对象-元类

实例对象、类对象、元类的获取代码如下: image.png 输出结果如下: image.png 从结果中可以看出,打印的地址都是同一个,所以 NSObject 只有一份,即 NSObject在内存中永远只存在一份。

总结1:

类的信息在内存中永远只存在一份,所以 类对象(类本身就是一个对象,这里说的不是类的实例对象。) 只有一份。

为什么[[obj class] class]方法获取不到元类?那原因是什么呢?查找 NSObject 源码得到 class 方法的源码:

image.png

总结2:

1, 一切皆对象。每一个对象都对应一个类。 Person 类就是person变量对象的类, 换句话说就是person对象的isa指向Person对应的结构体的类; class也是对象,描述它的类就是元类,换句话说class对象的isa指向的就是元类
2, 元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现, 如果没有则该元类会向它的父类查找该方法, 直到一直找到继承链的头。
3,对象的class 方法 本质是调用object_getClass,但是类的 class方法确是直接返回自己,也就是类,所以对象无论调用多少次 class 方法,返回的都是类自己

问题: 那么对象的class方法返回的是它的类型,类的class方法返回自己,怎么看类的类型呢?

解答: 我们看到object_getClass的源码如下:这个方法直接返回的是 isa 指针的指向,所以可以获取到元类

image.png

获取类的类型,我们需要 object_getClass([obj1 class])方法(相当于object_getClass(类名))来获取类的类型, 而不是 [[obj1 class] class]

综上:无论是类的对象,还是类,如果想获取他的类型是什么,都可以用object_getClass(xx)方法。

二: isa , objc_object(object对象) 和 Class (类) ?

通过# iOS底层原理02-objc对象的底层探索(下),在alloc最后的流程中是_class_createInstanceFromZone也就是计算出内存大小申请内存地址,并且返回指针,然后将isa指针cls类绑定在一起。最后的alloc的返回是 一个obj,它是id的类型。所有NSObject或其子类的alloc的方法返回出的类型都是objc_object *类型即id类型。id类型就是一个指向objc_object的指针类型。

Class类型在底层是一个结构体类型的指针,这个结构体类型为 objc_class。我们在源码中进行探索。

image.png

image.png

总结3:

1,NSObjec对象都是 C 语言结构体objc_object结构体的类型。
2,所有继承自 NSObject的类实例化后的对象都会包含一个类型为Class 的结构体,在这些对象的实例变量的结构体中第一个就是 isa, 这个结构体中包含了当前对象指向的类的信息
3,所有的 Class (也就是类)也会包含这一个 isa。在 ObjC 中Class 的定义也是一个名为objc_class 的结构体。Objective-C 中 Class (也就是类)也是一个对象
4,每个对象都有一个类,对象的类是isa指针决定的,即 isa 指针指向对象所属的类。

至此,可以得出结论:Objective-C也是一个对象。在objc_class中,除了isa之外,一般还有3个成员变量,一个是父类的指针superclass,一个是方法缓存cache,最后一个这个类的实例方法链表。当一个对象的实例方法被调用的时候,会通过isa找到相应的,然后在该类的class_data_bits_t中去查找方法。class_data_bits_t是指向了类对象的数据区域。在该数据区域内查找相应方法的对应实现。类方法调用时,通过类的isa 在元类中获取方法的实现。

类结构里面的一些元素:

isa 指针
我们之前的文章已经探索过了,在对象初始化的时候,通过isa 可以让对象和类关联,可是为什么在类结构里面还会有isa 呢?
其实就是我们的对象和类关联起来需要isa,同样的,类和元类之间关联也需要isaisa占用8个字节。

superclass 指针
superclass 指针表明当前类指向的是哪个父类superclass 指针占用8个字节。
一般来说,类的根父类基本上都是NSObject类根元类的父类也是 NSObject类。这点很好理解,比如我们自定义一个Person类,它继承自NSObjectNSObject就是继承自objc_object结构体(c写的),objc_object结构体里面有个isa指针结构体,是一个Class结构体,而Class结构体又继承自objc_object结构体。superclass占用8个字节。

cache 缓存
cache 的数据结构为cache_t,顾名思义是一些缓存的信息,总共占用16个字节,其定义如下:

image.png

bits 属性
bits的数据结构类型是class_data_bits_t,同时也是一个结构体类型。

image.png

发现了一个好玩的关键字 friend , 我们来查看一下该关键字的定义:
友元函数
友元函数是可以直接访问类的私有成员的非成员函数。 具体作用我们后面再看

image.png

image.png

bits 中存储的信息,其类型是class_rw_t,也是一个结构体类型
根据上面的指针和内存偏移内容可以知道,如果我们知道类的地址,并且属性是按照顺序依次排列的,只要我们知道isa、superclass、cache的内存大小,那我们就可以通过内存偏移来得到bits的内存地址,然后取出bits里面的内容。
在这个class_rw_t结构体中我们惊奇的发现这里有methods(方法)properties(属性)protocols(协议)这些信息,那么我们所需要的类中的方法、属性、成员变量等信息是不是在这里存储的呢?下面我们就用代码来验证下。\

image.png

先定义一个Person类,里面有 属性:name,属性:age,成员变量: hobby,对象方法 instanceMethod,类方法classMethod,然后我们通过lldb指令打印查看,结合上面的分析,来看看这几个成员都存储在了什么地方

image.png

在找bits的时候是通过内存偏移方法来找到,这也就是开头先补充的内存偏移的概念。 因为在objc_class的结构中,isa占8字节superclass占用8字节,cache占用16个字节,将cls的地址偏移32个字节即0x100008288便是bits的地址。

这里我们就找到了class_rw_t 结构体,接下来继续查看methods,properties,protocols这几个数组,来看看我们要找的方法、属性、协议等是不是在里面

image.png

image.png

那么怎么查看methods里面的值呢,我们继续

image.png

首先发现methods是一个method_list_t结构体,里面有个listlist下面有个ptr,我们先尝试到ptr

image.png

还是没能清晰,还得继续找,目前发现count = 7,也就是应该有7个方法,都是什么确看不到,现在发现了一个entsize_list_tt结构体,有method_tmethod_list_t结构体参数

image.png

image.png

找到了两个看似能用的 get()getDescription() 赶紧尝试一下

image.png

搞定了一个

image.png

同理都找到出来

image.png

这个是ARC下的析构函数,后面会详细说到 (objc_method_description) $18 = (name = ".cxx_destruct", types = "v16@0:8")

通过以上打印可以看到,在class_rw_t中找到了我们所定义的name/age属性、instanceMethod、name/age的setter/getter方法,但是成员变量hobby和类方法classMethod都没有找到。

总结4: bits 中存储的信息,其类型是class_rw_t,也是一个结构体类型。在class_rw_t中有属性、属性的setter/getter方法、对象方法。但不包括成员变量类方法

此时再从class_rw_t找一找其他线索,发现有一个class_ro_t *ro的东西,这是一个常量结构体指针,那么我们要找的成员变量和类方法会不会在这里呢,点进去看一下

image.png

image.png

进来一看发现,这里竟然跟class_rw_t看起来差不多,同样有方法、属性、协议列表,而且还有一个ivars 列表,那么这个ivars会不会就是成员变量列表呢。接下来继续用lldb指令来查看

image.png

果然,我们定义的成员变量hobbyagename是在class_ro_t里面的。同样在baseMethodList、baseProperties里面也找到了我们所定义的属性和对象方法,这里就不截图了。

总结5:

class_ro_t中有成员变量,并且存在class_ro_t的ivars里面
1.在class_rw_t里面存放的有methods、properties、protocols
2.在class_ro_t里有baseMethodList、baseProtocols、baseProperties、ivars
3.class_ro_t这个结构体是通过const定义,说明在编译时候就确定好了,后面取出来使用是不可以更改的。
4.成员变量不生成setter/getter方法,并且存在class_ro_t的ivars里面。
5.此时还有一个类方法classMethod没有找到。

下篇我们来讨论本章的遗留问题:
1.类方法存储到哪了?
2.rorwrwe是什么?
3.苹果设计元类的目的是什么?