Objc底层Alloc流程

Objc底层 alloc流程记录

引用Objc开源版本为906.2

1. callAlloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
// 编译优化,cls一般不为空,该判断为false概率较大
if (slowpath(checkNil && !cls)) return nil;
// 判断一个类是否有自定义的allocWithZone
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}

// No shortcuts available.
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}

slowpathfastpath__builtin_expect为0和为1对应的宏定义,目的是告诉编译器输入参数为false或true的概率较大,减少指令跳转。

2. _class_createInstance

这部分是alloc操作的核心代码,主要分三部分:

  1. 计算对象大小
  2. 按计算出的大小分配内存
  3. 将类与isa关联
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
NEVER_INLINE
id
_objc_rootAllocWithZone(Class cls, objc_zone_t)
{
// 可以看到zone参数已经不再适用,并没有往接下来调用的方法传递该参数
return _class_createInstance(cls, 0, OBJECT_CONSTRUCT_CALL_BADALLOC);
}

static ALWAYS_INLINE id
_class_createInstance(Class cls, size_t extraBytes,
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;

// 计算需要开辟的内存大小
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;

// 分配内存
id obj = objc::malloc_instance(size, cls);
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}

if (fast) {
// 关联class与isa
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);
}

在展开讲instanceSize()的实现之前,需要先理解内存字节对齐。

为什么要字节对齐以及对齐规则

尽管内存中以字节为单位进行存储,处理器却是以块为单位进行读取和写入,所以数据在内存中的存放需要进行对齐以减少CPU的工作量。每个特定平台上的编译器都有默认的对齐系数。gcc中默认为# pragma pack(4),该预编译命令可以用来设置对齐系数。

假如对齐系数是4,是否就一定按4个字节去进行内存布局呢?不一定,这里引入一个重要的概念—有效对齐值,而有效对齐值 = min(结构体中最长的数据类型,对齐系数),也叫对齐单位

了解了对齐单位的概念后,可以理解内存对齐需要遵循的规则了:

  • 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体的offset都是对齐单位的整数倍
  • 结构体总大小为对齐单位的整数倍

instanceSize

接下来看看instanceSize的实现,了解下alloc过程中如何计算出对象的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// defined in objc_class
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
// 从缓存快速读取大小
return cache.fastInstanceSize(extraBytes);
}

size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}

// defined in cache_t
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
// __builtin_constant_p内建函数用于判断一个值是否是编译时的常数
if (__builtin_constant_p(extra) && extra == 0) {
// 从flags中直接取出size
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}

// 按16位对齐算法
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
/**
以8来举例看看该算法的计算过程,看是怎么一步步填充成16的
align16(8)
= (8 + 15) & ~(15)
= 0000 0000 0001 0111 & ~(0000 0000 0000 1111)
= 0000 0000 0001 0111 & 1111 1111 1111 0000
= 0000 0000 0001 0001 0000
*/

malloc_instance

1
2
3
4
5
6
7
8
9
10
11
12
static inline id
malloc_instance(size_t size, Class cls __unused)
{
#if _MALLOC_TYPE_ENABLED
malloc_type_descriptor_t desc = {};
desc.summary.type_kind = MALLOC_TYPE_KIND_OBJC;
return (id)malloc_type_calloc(1, size, desc.type_id);
#else
// 打断点发现会走到这里
return (id)calloc(1, size);
#endif
}

可以看到malloc_instance底层也是调用calloc进行内存分配,返回指向分配好的内存的指针,到这里为止内存空间已经有了,但这时候打印对象的话,是看不到类信息的,因为isa还没进行初始化,也就是类的信息还没关联进去。

initInstanceIsa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());

isa_t newisa(0);

if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());


#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
#if ISA_HAS_INLINE_RC
newisa.extra_rc = 1;
#endif
}

// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa() = newisa;
}

大概流程就是初始化一个isa,让isa与类进行关联,并指向申请的内存地址。执行完这一步可以在lldb中把对象po出来,可以看到输出的信息中除了内存地址,类名也带上了。

总结

alloc流程看下来主要是三步,首先是计算需要的内存大小,计算过程需要进行16字节对齐,分配好内存后初始化isa关联类的信息。