李峰峰博客

OC 对象一探究竟之三:alloc 与 init

2020-03-29

一般我们都是通过如下方式创建一个对象:

1
MyObject *objc = [[MyObject alloc] init];

那么,allocinit 方法都分别做了什么事呢?

1、alloc

alloc 方法实现如下:

1
2
3
+ (id)alloc {
return _objc_rootAlloc(self);
}

可以看到,内部就是调用了 _objc_rootAlloc 函数:

1
2
3
4
5
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

callAlloc 函数实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// 如果类没有实现自定义的 +allocWithZone 方法
return _objc_rootAllocWithZone(cls, nil);
}
#endif

// 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));
}

以上逻辑是先判断是否实现了自定义的 +allocWithZone 方法
如果实现了,通过 objc_msgSend 方式直接调用自定义的 +allocWithZone 方法
如果没有实现,则调用 _objc_rootAllocWithZone 函数

一般我们不会自定义 +allocWithZone 方法,所以接下来看下 _objc_rootAllocWithZone 函数的实现:

1
2
3
4
5
6
7
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}

_objc_rootAllocWithZone() 内部进入到了 alloc 源码的核心操作 _class_createInstanceFromZone() 函数,其实现如下:

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
52
53
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());

// 类或者父类是否有 C++ 构造方法或析够方法
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
//是否能alloc nonpointer isa
bool fast = cls->canAllocNonpointer();
size_t size;
// 计算对象所需大小,这里 extraBytes == 0,由前一函数传递
size = cls->instanceSize(extraBytes);
// 将计算好的 size 通过指针地址传递出去,不影响我们理解主流程,可以忽略
if (outAllocatedSize) *outAllocatedSize = size;

id obj;
// 判断是否有 zone,在之前的版本是有 zone 的
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// alloc 开辟内存的地方
obj = (id)calloc(1, size);
}
// 这里是内存开辟错误执行的流程,字面意思可以看到,系统调用了 alloc 失败句柄
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}

if (!zone && fast) {
// 初始化 isa,并将类与 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);
}

// 若没有 C++ 构造函数,则将关联好类的 isa 返回
if (fastpath(!hasCxxCtor)) {
return obj;
}
// 若有 c++ 构造函数,则走 c++ 构造流程
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}

这里主要是下面三个主要逻辑:

  • cls->instanceSize():计算需要开辟的内存空间大小,也就是创建对象所需的大小,这里会对对象做 16 字节对齐。
  • calloc():通过 instanceSize 计算的内存大小,向内存中申请对应大小的内存,并赋值给 obj,因此 obj 是指向内存地址的指针。
  • obj->initInstanceIsa():初始化 isa,并将类与 isa 关联。

2、init

init 方法源码如下:

1
2
3
4
5
6
7
8
9
10
11
- (id)init {
return _objc_rootInit(self);
}

id
_objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function.
// Many classes do not properly chain -init calls.
return obj;
}

可以看到 init 的实现仅仅是返回传入的 self 本身,可以理解为就是一个空壳,开发者可以根据需要增加自定义的 init 实现。也就是说,调用了 alloc 方法之后,无需调用 init 方法,就能直接调用实例方法了,因为这个时候对象已经创建好了。但是实际开发中不要这么做,因为对应的类中很有可能有自定义的 init 方法。

3、new

new 方法实现如下:

1
2
3
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}

可以看到,new 方法实际上就是等价于 [[XXXObject alloc] init];