OC底层原理05:objc_alloc探索

张建 lol

前言

本章主要探索继承自 NSObject 的类 ZJPersonNSObject 调用 alloc 的区别

准备工作

1、下载objc4-781 源码。

2、编译源码,可参考iOS-OC底层原理02:Objc4源码编译

NSObject调用alloc探索

1、第一步:在main函数中实现下面的代码,并下断点:

2、第二步:到源码下断点到 + (id)alloc,先 取消断点,等程序运行到目标行在去跟踪断点,原因在前面查找源码已经讲过,在此不在说明:

我们发现并没有进来,我滴天呐,说明调用的不是 alloc

3、第三步:我们换一种方式去查看源码,打开Debug->Debug WorkFlow->Debug->Debug Workflow->Alway Show Disassembly查看汇编:

我们发现底层源码调用的是 objc_alloc ,到底是不是呢?接下来我们 二次验证 一下

**4、第四步:二次验证->先取消Debug查看方式,到源码中下断点到 objc_alloc **

我们发现 Class cls 这个类确实是 NSObject ,验证了我们上面的调用过程

5、第五步:继续往下走,callAlloc 内部调用流程

我们发现 callAlloc 内部最终调用的是 _objc_rootAllocWithZone

最终和iOS-OC底层原理03:alloc&init&new探索alloc调用流程不谋而合

6、结论:我们用流程图去表达一下调用流程

继承类ZPerson调用alloc真实调用流程

1、第一步:在 main 函数中实现 ZJPerson 类的 alloc 方法进入alloc方法,并下断点:

2、第二步:打开 Debug->Debug WorkFlow->Debug->Debug Workflow->Alway Show Disassembly 查看汇编:

3、第三步:通过在源码中下断点 objc_allocalloc 去跟踪具体先执行过程:

1
2
3
4
5
6
7
跳转至objc_alloc的源码实现
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}

4、第四步:跳转至 callAlloc 的源码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
// checkNil:true
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// cls NSObject
return _objc_rootAllocWithZone(cls, nil);
}
#endif

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

5、第五步:跳转至 return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc)); 的内部源码实现

1
2
objc_msgSend(void /* id self, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

系统通过特殊消息发送:sel - imp 做了一层处理,最终执行到 alloc,根本就是系统通过LLVM特殊消息发送做的处理,有兴趣的小伙伴可以通过LLVM源码去探索一下

6、第六步:跳转至 alloc 源码实现

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

7、第七步:跳转至 _objc_rootAlloc 源码实现,我们可以验证 clsZJPerson

8、第八步:跳转至 _objc_rootAllocWithZone 源码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
// checkNil:true
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// cls NSObject
return _objc_rootAllocWithZone(cls, nil);
}
#endif

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

9、第九步:跳转至 _class_createInstanceFromZone 源码实现

1
2
3
4
5
6
7
8
NEVER_INLINE
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);
}

10、第十步:内部调用 cls->instanceSize、calloc、obj->initInstanceIsa(cls, hasCxxDtor)、return obj 方法返回对象,具体流程不再赘述

11、总结:ZJPerson的alloc调用流程图:

ZJPerson的alloc和NSObject的alloc区别

  • ZJPerson 会走两次 objc_alloc -> alloc

  • NSObject 只走一次 objc_alloc

  • Post title:OC底层原理05:objc_alloc探索
  • Post author:张建
  • Create time:2020-09-12 02:04:27
  • Post link:https://redefine.ohevan.com/2020/09/12/OC底层原理/OC底层原理05:objc-alloc探索/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.