如何探索底层 + alloc 做了什么

本文介绍 如何探索 alloc 和 alloc 做了什么?

objc 可编译源码

从最简单的代码开始:

1
2
3
4
5
6
7
MyPerson *p1 = [MyPerson alloc];
MyPerson *p2 = [p1 init];
MyPerson *p3 = [p1 init];

NSLog(@"%@ - %p - %p",p1,p1,&p1);// p1 - 对象 - 指针
NSLog(@"%@ - %p - %p",p2,p2,&p2);
NSLog(@"%@ - %p - %p",p3,p3,&p3);

打印结果如下:

p1/p2/p3:对象是同一个 MyPerson,指针指的同一个对象,指针地址不同。且其指针地址相差 8 字节,为什么呢?

alloc 做了什么?我们点击 alloc 无法查看实现,只能停留在 NSObject.h 中?

一、如何查找实现 ku?

查找实现库

1、符号断点:

run: –> [NSObject alloc] –> libobjc.A.dylib

2、普通断点

按住:control + step into –>

添加符号断点:objc_alloc –> libobjc.A.dylib

3、汇编查看

debug->debug workflow -> always show disassembly

运行:

control + step into –>  如下图 –> 添加 符号断点 objc_alloc –> libobjc.A.dylib

objc 源码下载 - 地址:最新版本 objc4-781

二、进入源码 – 流程分析

打开下载的源码文件,由 C C++ 汇编共同编写。

1、源码分析

1.1)alloc 入口:

 

我们通过点进去,发现:

_objc_rootAlloc –> callAlloc –> _objc_rootAllocWithZone / objc_msgSend 2 者走谁?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
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));
}

可通过:回到 demo,添加符号断点:_objc_rootAlloc / callAlloc / _objc_rootAllocWithZone –> 运行

跟随断点,可知 alloc 走 _objc_rootAllocWithZone:

 

1.2)开辟空间 _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);
}

_class_createInstanceFromZone() –> 源码中 3 个方法 (标红位置)

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
_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) {// zone 已经废弃不走它 直接走 else
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 关联 起来
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);
}

思考,是否可以直接运行源码走进方法进行调试呢?

–> objc 源码编译调试 配置方法

配置成功后,run。

2、实际运行分析

2.1)开辟多少内存空间 instanceSize()

align16() –> size + 0 - 8 = 8; // size = 16

1
2
3
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}

字节对齐 (针对对象)– 最新的是 16 字节对齐,苹果之前的对齐方式是 8 字节对齐。16 字节更加安全,预留空间更多,不易产生野指针等。

每个对象都有继承自 NSObject 的 isa,一个指针 8 字节。

2.2)申请开辟内存空间 calloc()  –>  2.3)内存指针和 类 cls 绑定 initInstanceIsa()

三、init 和 new

init:

 

构造方法 (工厂),用来给我们自定义开发 - 重写。

new: –> alloc 的 callAlloc –> [MyPerson new];  ==》相当于 [[MyPerson alloc] init];

但,我们 init 重写的一些方法,在 new 是无法实现的。 <– 不同之处

以上。

tip:slowpath / fastpath 是什么? – 编译器优化

例如一些中间过程编译直接优化掉,节省时间,优化性能

如上图,release 是 Feastest,Smallest, 发包苹果也会帮我们进行优化。 

运行:

我们将 debug 也改成 fastest,smallest,再次运行,可看到中间编译过程已被优化掉:


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!