由一个系统系统调用可以一窥 iOS 的内存分配, isa 与 obj 是如何绑定到一个对象?
2024-01-03 03:54:29
前言
前面在《iOS 对象底层原理之 alloc 分析》中介绍了创建一个对象的关键三步:
- 计算类占用的内存大小
- 根据计算出来的类占用的内存大小 size,对于 size 进行 16 进制对齐,并在堆中开辟空间
- 类(i.e. [NSObject class]) 指向刚开辟的空间,将 Class 类的 isa 指针也指向开辟的空间
但我们知道对象不仅仅包括类成员,它还有实例变量,这些实例变量是怎么放到这个开辟的空间的呢?
对象中还包含了 isa 指针(用来指向类),这个 isa 指针是怎么放到对象的开始位置的呢?
isa & obj 如何绑定到一个对象
编译阶段
我们先从编译阶段讲起,先来看看我们创建了一个对象是如何编译和生成的。
// MyClass.m
@implementation MyClass
- (void)print{
NSLog(@"Hello, World");
}
@end
- 编译器会将这段代码编译成汇编代码,其中最关键的一段如下:
// MyClass.s
.section __TEXT,__text,regular,pure_instructions
.globl _OBJC_CLASS_$_MyClass
_OBJC_CLASS_$_MyClass:
.quad __objc_empty_cache
.quad 0 // isa 指针
.long 32 // 大小
.long 16 // 实例变量数量
.long 0 // ivar offset
.long _OBJC_METACLASS_$_MyClass
.long 0 // Cache data pointer
.long 0
.long 0
.long 0
.long 0
.long 0
.asciz "MyClass" // 类名
上诉代码中,我们需要注意的是 _OBJC_CLASS_$_MyClass
这段代码,它表示的是我们在 MyClass.m
文件中定义的类 MyClass
的类对象。
.long 0 // isa 指针
0
指的是 MyClass
类对象的 isa
指针,也就是 MyClass
类的 isa
指针。
我们知道类对象的 isa
指针指向元类,元类的 isa
指针指向 NSObject
类,NSObject
类的 isa
指针指向 _objc_rootClass
。
.long 32 // 大小
32
指的是 MyClass
类的大小,也就是 MyClass
类占用的内存大小。
.long 16 // 实例变量数量
16
指的是 MyClass
类中实例变量的数量。
.long 0 // ivar offset
0
指的是 MyClass
类中实例变量的偏移量,也就是 MyClass
类中第一个实例变量的偏移量。
.long _OBJC_METACLASS_$_MyClass
_OBJC_METACLASS_$_MyClass
指的是 MyClass
类的元类对象,也就是 MyClass
类对象的 isa
指针。
运行时
当我们运行程序时,会调用 objc_msgSend
函数来给对象发送消息。
objc_msgSend
函数会首先检查对象的 isa
指针,然后根据 isa
指针找到对象的类对象,再根据类对象的 cache
数据找到对象的方法实现,最后调用方法实现。
如果对象的 isa
指针指向的是元类对象,那么 objc_msgSend
函数会根据元类对象的 cache
数据找到元类的方法实现,最后调用元类的方法实现。
我们知道,对象的 isa
指针是存储在对象的开始位置的,那么 objc_msgSend
函数是如何找到对象的 isa
指针的呢?
内存对齐
在 iOS 中,内存是对齐的,也就是内存地址必须是 16 的倍数。
对象的大小也是对齐的,也就是对象的 size 必须是 16 的倍数。
当我们在堆中开辟空间时,系统会自动将 size 进行 16 进制对齐。
也就是说,如果对象的 size 不是 16 的倍数,那么系统会在对象的前面填充一些字节,使对象的 size 成为 16 的倍数。
填充的字节被称为 对齐字节 。
对象的布局
对象的布局如下:
+------------------+----------------+
| isa 指针 | 对齐字节 | 实例变量 |
+------------------+----------------+
isa 指针是存储在对象的开始位置的,对齐字节是存储在 isa 指针后面,实例变量是存储在对齐字节后面。
isa & obj 如何绑定到一个对象
当我们在堆中开辟空间时,系统会自动将 size 进行 16 进制对齐。
也就是说,如果对象的 size 不是 16 的倍数,那么系统会在对象的前面填充一些字节,使对象的 size 成为 16 的倍数。
填充的字节被称为 对齐字节 。
对象的布局如下:
+------------------+----------------+
| isa 指针 | 对齐字节 | 实例变量 |
+------------------+----------------+
isa 指针是存储在对象的开始位置的,对齐字节是存储在 isa 指针后面,实例变量是存储在对齐字节后面。
当我们在 objc_msgSend
函数中通过对象的 isa
指针找到对象的类对象后,我们会根据类对象的 cache
数据找到对象的方法实现,最后调用方法实现。
当我们在 objc_msgSend
函数中通过对象的 isa
指针找到对象的元类对象后,我们会根据元类对象的 cache
数据找到元类的方法实现,最后调用元类的方法实现。
总结
通过上面的分析,我们可以知道 isa
指针是存储在对象的开始位置的,对齐字节是存储在 isa
指针后面,实例变量是存储在对齐字节后面。
当我们在 objc_msgSend
函数中通过对象的 isa
指针找到对象的类对象后,我们会根据类对象的 cache
数据找到对象的方法实现,最后调用方法实现。
当我们在 objc_msgSend
函数中通过对象的 isa
指针找到对象的元类对象后,我们会根据元类对象的 cache
数据找到元类的方法实现,最后调用元类的方法实现。