OC对象的底层探索(上)

248 阅读3分钟

Xnip2022-04-18_10-37-06.jpg

一、alloc方法在底层调用的流程

1、alloc调试

1.1、引入查看如下代码:

   LGPerson** *p1 = [p init];
   LGPerson** *p2 = [p init];
   NSLog(@"p = %@", p);
   NSLog(@"p1 = %@,p2 = %@",p1,p2);

输出如下: Xnip2022-04-18_11-35-16.jpg

从上面可以看出p、p1和p2指向同一内存空间,alloc开辟了内存,而init只是这个类的构造方法,并没有开辟内存的功能。可以用下图表示:

Xnip2022-04-18_12-16-42.jpg

1.2、alloc汇编调试

1.2.1、一些简单的汇编指令:

  • b bl 跳转指令 -- 函数的调用
  • ret: 函数的返回
  • ;注释
  • register read 汇编读数据 其中x0 是 self,x1 是 cmd(方法名),2 个默认参数

1.2.2、XCode编译器调试(在走每个断点前要不上一个符号断点断开,不然系统方法会走到前面断点中)

  1. 如下图设置: image.png

  2. 在alloc断点如下 image.png

  3. 进入objc_alloc方法(objc_alloc) image.png

  4. 符号断点objc_alloc,按照步骤走后发现调用到objc_msgSend,用寄存器读取x0,x1,可以得到,objc_msgSend调用alloc方法(objc_msgSend) image.png

  5. 符号断点 [NSObject alloc] ~~(打[NSObject alloc]断点就是为了防止系统的其他类在调用alloc方法而对我们的调试产生影响)~~又进入alloc方法,(alloc)如下图 image.png

6.进入_objc_rootAlloc方法(_objc_rootAlloc): image.png

7.进入_objc_rootAllocWithZone 方法(_objc_rootAllocWithZone) image.png 在 retab(函数的返回)断点初始化完成: image.png 从汇编探索的alloc流程如下: alloc->objc_alloc->objc_msgSend->alloc->_objc_rootAlloc->objc_rootAllocWithZone

1.3、alloc查看源码

  1. 源码地址版本objc4-838.1 2.allcoc image.png

3.调用objc_alloc image.png

4.callAlloc->objc_msgSend image.png

5.alloc image.png

6._objc_rootAlloc image.png

7.callAlloc image.png

8._objc_rootAllocWithZone image.png

  1. _class_createInstanceFromZone image.png

从源码探索可以看出alloc的流程如下: alloc->objc_alloc->callAlloc->objc_msgSend->alloc->_objc_rootAlloc->callAlloc->_objc_rootAllocWithZone->_class_createInstanceFromZone 从中可以得出alloc是实际创建对象的方法

2. init的探索

1.1 [NSObject init]符号断点-可以在汇编看到只是执行了ret返回如下图

image.png

1.2 查看源码也是返回obj

image.png image.png init是一种工厂模式,去初始化对象等操作。

二、编译器的优化

从上面汇编的调试和源码的查看我们可以看到汇编的调试alloc流程中比源码中少了callAlloc_class_createInstanceFromZone这两个步骤,这是因为编译器对我们代码进行了优化。 编译器的优化等级可以在target->Bulid Settings->Optimization Level中设置,一般在Debug模式下是None,Release模式下是Fastest,Smallest。如下图: Xnip2022-04-18_23-13-34.jpg

三、对象的内存对齐方式

1. 开辟内存流程

alloc方法最终在_class_createInstacnceFromZone方法中创建实例对象返回,在阅读其中代码我们可以看到alloc底层计算对象内存大小,是instanceSize函数如下: image.png 系统开辟内存是在calloc方法中最终进入 _nano_malloc_check_clear方法中,此方法中重要的函数是segregated_size_to_fit方法如下: 其中在64位机器中宏定义为:#define NANO_REGIME_QUANTA_SIZE ( 1 << SHIFT_NANO_QUANTUM) // 16 image.png 从字节算法可以看出在clloc方法中是16字节对齐的

2.字节算法

在instanceSize函数如下中如果没有缓存走alignedInstanceSize方法如下,最终进入word_align方法中的8字节算法中 image.png 总结:在对象内部以8字节对齐,但是在开辟内存时16字节对齐的,最要是为了空间换区时间,是CPU读取的时候更高效。

四、对象的本质

1. Clang

  • Clang 是C语言、C++Objective-C语言的轻量级编译器
  • Clang可以将目标文件编译成C++文件:clang -rewrite-objc main.m

2. Clang分析OC对象的本质

定义一个类如下: image.png Clang编译以后的文件如下: image.png 其中NSObject_IMPL如下: image.png 由此可以看出本质是objc_object,存储了isa指针成员变量的值

五、结构体的内存对齐方式

1.基本类型占的字节数

image.png

2.结构体内存对齐规则

  • 数据成员对齐规则:结构(struct)的第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储)。 
  • 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储)(例子struct5)。
  • 收尾工作:结构体的总大小,也就是sizeof的结果必须是其内部最大成员的整数倍,不足的要补齐。

3.实例分析

image.png image.png

六、总结

  1. alloc方法底层调用流程图: 未命名文件.jpg

  2. alloc的核心方法:

  • cls-instanceSize()计算对象需要的内存大小;
  • calloc() 系统实际给对象分配内存大小计算;
  • obj->initInstanceIsa关联到实际对象;
  1. 对象内部的成员变量以8字节对齐,系统实际分配的内存以16字节对齐;这样做的好处是空间获取时间。因为OC对象中必定有ISA指针,isa指针占8个字节,那么对象最少要占8个字节。如果以8个字节对齐,如果连续两块内存没有属性对象,那么它们的内存空间完全挨在一起,容易造成内存混乱。以16字节为⼀块,这就保证了CPU在读取的时候,按照块读取就可以,效率更⾼,同时还不容易混乱;

  2. 对象的本质是objc_object结构体,里面存有isa和成员变量的值.