在进行源码跟踪之前先了解一下以下的三个方法:
sizeof:判断数据类型或者表达式长度的运算符,返回一个变量或者类型的大小(以字节为单位)。class_getInstanceSize:返回类实例的大小。malloc_size:系统分配的内存大小。
sizeof、class_getInstanceSize 与 malloc_size 打印的结果不一致。原因是sizeof 和 class_getInstanceSize 是获得这个对象需要占用的内存。malloc_size 是系统会给这个实例对象分配的内存。
一、alloc 源码跟踪
我们打开可编译的源码,对alloc跟踪,大致的流程是这样的:alloc -> _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone -> _class_createInstanceFromZone。
它的核心方法是_class_createInstanceFromZone,来看一下源码的实现:
这个方法共有三个核心步骤,先来看看第一个。
二、 instanceSize 计算对象需要开辟的内存
instanceSize的实现大致可以分两个步骤:
- 缓存中快速计算内存大小:
cache.fastInstanceSize。 - 计算 isa 和成员变量需要的内存大小(
alignedInstanceSize),如果不足16字节的,补齐16字节。
fastInstanceSize的源码实现:
可以看到,在最后,会调用一个 align16 的函数,来看一下这个函数的实现:
发现,这是一个内联函数,并且,它的作用就是,传一个size_t,通过size_t的大小,计算并返回size_t 16字节对齐后的大小。
为什么是16字节对齐,其实是可以算出来的,假设参数 x = 8,就变成了 (8 + size_t(15)) & ~size_t(15),~有取反的作用,这个值在二进制中的0变成1,1变成0的意思。具体的算法看下图:
通过这张图可以得知,align16 就是进行16字节对齐的一个函数。
alignedInstanceSize的源码调用
可以看到,alignedInstanceSize就是拿到类的成员变量的大小,进行word_align。那word_align是什么。来看一下它的实现:
说白了,alignedInstanceSize的最终目的就是为了拿到类的成员变量的大小进行8字节对齐。这也是为什么会有不满16字节,补齐16字节的判断。
另外,instanceSize方法只是计算出当前类开辟内存时需要的大小,不能决定在运行时当前类所占用的内存就是这个大小,决定当前类在运行时占用的内存大小由 calloc 函数决定。下面,来看一下这个calloc。
三、 calloc -> 开辟内存
在 libmalloc-317.40.8 源码中,calloc的流程:
calloc -> _malloc_zone_calloc -> default_zone_calloc -> nano_calloc -> _nano_malloc_check_clear -> segregated_size_to_fit。
直到 segregated_size_to_fit 方法中,查看 NANO_REGIME_QUANTA_SIZE 的定义发现以下宏定义:
#define SHIFT_NANO_QUANTUM 4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
#define NANO_QUANTA_MASK (NANO_REGIME_QUANTA_SIZE - 1)
#define NANO_SIZE_CLASSES (NANO_MAX_SIZE/NANO_REGIME_QUANTA_SIZE)
可以得知,calloc 开辟内存是遵守 16 字节对齐的。什么意思呢,就是在instanceSize计算出当前类的内存大小后,由calloc对当前类的内存大小进行16字节对齐,这个才是系统分配给当前类在运行时真正占用的内存大小。
这就是前面提到的 sizeof、class_getInstanceSize 与 malloc_size 打印的结果是不一样的原因。
四、initInstanceIsa
initInstanceIsa最终会调用initIsa,来看一下initIsa的源码实现:
其实这个方法就是将 cls 和 isa 绑定在一起,进行关联。