前言
上篇博客说到了对象alloc初始化的三个步骤,今天就来详细说下第一步cls->instanceSize(extraBytes);
做了什么。
先上代码,这是上篇博客通过追踪alloc源码追到了这里
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
// 1:要开辟多少内存
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// 2;怎么去申请内存
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
// 3:将 cls类 与 obj指针(即isa) 关联
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
这时候我们点击第一步计算该类cls需要开辟多大内存的函数cls->instanceSize(extraBytes);
跳转进去。下图为源码
size_t instanceSize(size_t extraBytes) const {
//1.编译器快速计算内存大小
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
// 2.计算类中所有属性的大小(内存对齐) + 额外的字节数0
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
//3.如果size 小于 16,最小取16
if (size < 16) size = 16;
return size;
}
这个时候我们把目光聚焦在第二步和第三步,这里涉及了两个知识点 第二步:内存对齐,第三步16字节对齐。
内存对齐
1.结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
iOS中Xcode默认为#pragma pack(8),即8字节对齐。
通俗来说记住三点
1.结构体中每个数据成员的偏移位置是数据成员本身尺寸的倍数,可以理解为min(m, n) 的公式, 其中 m表示当前成员的开始位置, n表示当前成员所需要的位数。如果满足条件 m 整除 n (即 m % n == 0), 成员n 从 m 位置开始存储, 反之继续检查 m+1 能否整除 n, 直到可以整除, 从而就确定了当前成员的开始位置。
2.结构体的尺寸是最大基础类型数据成员尺寸的倍数。
如果有结构体嵌套时,被嵌套的结构体成员的偏移位置就是被嵌套结构体中尺寸最大的基础类型数据成员尺寸的倍数。嵌套结构体的尺寸则是所有被嵌套中的以及自身中的最大基础类型数据成员尺寸的倍数。
给一张图大家对照类型计算
我举个例子,下面这张图我写了四个结构体然后分别打印了他们所需要的内存大小,不知道大家根据上面的内存对齐规则算的对不对。
接下来我通过表格给大家展示下为什么每个结构的大小是这样,我们先来看A和B。
看了上面表格里面的说明,大家应该明白了基本内存对齐规则,那么结构体C为什么需要32字节内存,大家知道吗,下面继续表格走起。
看了两个表格的详细说明,想必大家都清楚了结构体开辟内存的计算规则,对象同理(oc里面对象都是由结构体封装的,结构体第一个成员为isa(8字节)),苹果现在规定对象初始化最小内存为16字节,不足16则申请16字节,为了防止对象之间隔得太近读取出错,也为了后续扩展,这也是之前代码
cls->instanceSize(extraBytes);
函数里面为什么有这么一个判断。