类的结构分析 & isa & bits

本文内容主要对 isa 指向流程 和 类的结构以及类中 bits 进行探索。

一、类与 isa

运行 objc 源码工程,main.m 文件中断点打在 objc2,读取对象 objc2 的内存如下:

图中,我们发现两个不同的 地址,他们的值都是 MyPerson。这是为何呢?–> 元类

我们继续读取内存,如下图:

结合以上内容,我们可以得出 objc2 –> MyPerson –> MyPerson –> NSObject 

即:isa 对象 –> 类 –> 元类 –> NSObject 类

上图中,通过 NSObject.class 看到: NSObject –> NSObject

有 2 个不同地址的 NSObject ,它又是为何呢?内存中会存在多个 NSObject 对象吗?

不会!类对象在内存中只存在一份!!!

我们以 MyPerson 简单验证类对象在内存中只有一个:

 

alloc 多次,class 地址相同,只有一个。

isa 指向流程 

从上面第二幅图中可以观察到 (注意黄线标识部分),NSObject 类,内存读到  0x00000001003ee0f0 后,开始指向自己。

–> 实例对象 –> 类 –> 元类 –> NSObject 根元类 –> NSObject 根元类

isa 指向图:

小小注意点:

实例对象之间时不存在关系的,所谓的父子继承是针对类而言。

NSObject(Root class) 的父类是 nil,NSObject 的元类是根元类 (root),根元类的父类是 NSObject,根元类的元类是自身

OC - NSObject 万物之源!!!

二、类与对象

1、objc_object 和 objc_class

通过源码查找:

objc_object - 对象 : –> OC 的 NSObject 底层 –> objc_object 结构体

objc_class - 类 :–> 继承自 objc_object

所有对象、类、元类都有 isa,它们是来源于继承的 objc_object 里面的 isa. 

对象 –> objc_object

类    –> objc_class

而 objc_class  – 继承自 –>  objc_object –> class 也是个对象 –> 万物皆对象

–> 引申:类是什么?类是元类的实例对象。

问题:objc_object 和 对象的关系

从上面我们可知,所有的对象本质上都是基于 objc_object 为模板创建的。

OC 的底层其实就是 C/C++,OC 相当于一层封装。以结构体 objc_object 为模板,创建了所有 OC 对象,进而提供给我们使用。苹果的大特质就是封装啊。

本质 - 万物皆 objc_object !!!

objc_object / objc_class / object / NSObject / isa 关系图:

三、类的结构分析

通过源码查看结构:

在继续探索类之前我们要先简单介绍下指针和内存偏移。

1、扩展 - 指针和内存偏移 

1、普通指针

 

指针地址不同,浅拷贝 也称值拷贝。

2、对象指针

objc1 objc2 指针 指向 [MyPerson alloc] 开辟的空间 - 内存地址;

&objc1 &objc2 指针的地址 –> 二级指针。

3、数组指针

如上图,*p 指针指向 arr,即 arr 首地址。通过指针偏移,可一次获取到 arr 中数据。

2、类 的结构和内容 

isa:继承 objc_object 的 isa ,8 字节

superclass:Class 类型 指针,8 字节

catch_t :结构体,结构体大小的计算我们在内存对齐中有介绍,它是内部全部属性的大小,且要遵循内存对齐原则。

catch_t 的大小计算:

if –>

   bucket_t: struct bucket_t * 指针类型 8 字节;

   mask_t: uint32_t  4 字节;

elif –>

    uintptr_t:指针类型 8 字节;

    mask_t:uint32_t   4 字节;

其他 –>

    __flag:unsigned short uint16_t  2 字节

  _occupied:unsigned short uint16_t  2 字节

8 + 4 + 2 + 2 = 16 字节  

3、类中的信息 - bits

1、找到类信息

1、直接拿到类的首地址方式 **(lldb)**:bits 前面有三个变量 8+8+16 = 32.

p/x MyPerson.class

x/4gx MyPerson.class

2、计算 bits 地址 –> 首地址平移 32 字节:

  16 进制,32 即进位 2 –> 16 进制地址 第二位 + 2

3、通过源码 1250 行 (见下图),得知 bits 中有个 ‘class_rw_t  *data’ 的 data。

拿到 data:$2->data().

读取到 data:p *$4

  

查看 bits 流程 –> 类的首地址 + 32 –> bits 地址 –> (class_data_bits_t *)bits 地址 –> bits.data() –> (class_rw_t )data{} 

如上图,我们看到了 $5 中的一些信息,但是我们的属性方法列表在哪里呢?

2、bits

MyPerson 代码 (这里为了便于区分后来将定义的 name 改为 propName 了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@interface MyPerson : NSObject {

NSString *instName;
}

@property (nonatomic, copy) NSString *propName;

- (void)funcObjcTest;
+ (void)funcClassTest;
@end

@implementation MyPerson
- (void)funcTest {

}
+ (void)funcClassTest {

}
@end

class_rw_t

点击进去,比较长,一直滑到下面,如图:

 

由源码我们可知,class_rw_t 中有 方法、属性、协议 列表 。

1、property_list 属性列表

根据源码,我们这里进行 property() 的查询:

 

propety_list 中:只有一个 “name” 属性存在。

2、method_list 方法列表 - 实例方法

p $21.get(4) error 数组越界。 

method_list 中:propName 的 setter/getter 方法和 实例方法 funcTest。setter/getter 方法由系统自动帮我们生成。

         .cxx_destruct:OC 封装于 C++ 底层,默认添加。

从上可知,我们的实例变量和类方法并不在 property() 和 method() 中,继续探索。

3、class_ro_t 实例变量

结构体 class_rw_t 中 set_ro –> class_ro_t

 

class_ro_t

通过源码,可以猜测实例变量存在 ivars 中,我们做如下验证作操作:

如上,我们可知,实例变量和属性变量的_propName 都在 ivars 中。属性变量、成员变量简介.

我们继续寻找类方法在哪里。 

通过文章上面的 isa 探索部分,我们知道 实例指向类,类指向元类;实例方法在类中,那么类方法是否在元类中呢?

4、验证类方法位置

如上,我们通过 MyPerson.class 的 isa,拿到元类,然后进行地址平移 32 字节,之后操作如图,果然,类方法在其中找到。

同时,这里也可验证我们上面所探究的 isa 流向。

总结:

isa:

  实例    –>  类

  类       –>  元类

  元类    –>  根元类

  根元类 –>  根元类

方法:

  实例方法 –> 类中

  类方法    –> 元类中

变量:

  属性变量                –> propety_list

  成员变量 /_属性变量 –> ivars


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!