OC 底层探索 04、类的结构分析 & 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了)

@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

 

posted @ 2020-09-14 00:01  张张_z  阅读(337)  评论(0编辑  收藏  举报