注册
iOS

iOS 底层原理探索 之 alloc

df4ae5d53e084e7c8c6131fffb3a45e8~tplv-k3u1fbpfcp-zoom-crop-mark:1304:1304:1304:734.awebp

iOS 底层原理探索 之 alloc

写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。


内容的总结专栏


作为一名iOS开发人员,在平时开发工作中,所有的对象我们使用最多的是alloc来创建。那么alloc底层做了哪些操作呢?接下来我会一步一步探究alloc方法的底层实现。

初探

我们先来看下面的代码

    SMPerson *p1 = [SMPerson alloc];
SMPerson *p2 = [p1 init];
SMPerson *p3 = [p1 init];

NSLog(@"%@-%p-%p", p1, p1, &p1);
NSLog(@"%@-%p-%p", p2, p2, &p2);
NSLog(@"%@-%p-%p", p3, p3, &p3);

打印内容:

    <SMPerson: 0x600000710400>-0x600000710400-0x7ffee6f15088
<SMPerson: 0x600000710400>-0x600000710400-0x7ffee6f15080
<SMPerson: 0x600000710400>-0x600000710400-0x7ffee6f15078

可见,在 SMPerson 使用 alloc 方法从系统中申请开辟内存空间后 init方法并没有对内存空间做任何的处理,地址指针的创建来自于 alloc方法。如下所示:

地址.001.jpeg

注:细心的你一定注意到了,p1、p2、p3都是相差了8个字节。 这是因为,指针占内存空间大小为8字节,p1、p2、p3 都是从栈内存空间上申请的,且栈内存空间是连续的。同时,他们都指向了同一个内存地址。

那么, alloc 是如何开辟内存空间的呢?

首先,第一反应是,我们要Jump to Definition,

2241622899100_.pic_hd.jpg

结果,Xcode中并不能直接跳转后显示其底层实现,所以 并不是我们想要的。

2251622899278_.pic_hd.jpg

WX20210605-214250@2x.png

中探

接下来,我们通过三种方法来一探究竟:

方法1

既然不可以直接跳转到API文档来查看alloc的内部实现,那么我们还可以通过下 符号断点 来探寻 其实现原理。

WX20210605-212725@2x.png

接下来我们就来到此处

WX20210605-213213@2x.png

一个名为 libobjc.A.dylib 的库,至此,我们就应该要去找苹果开源的库,以寻找我们想要的答案。

点击查看苹果开源源码汇总

方法2

我们也可以直接在alloc那一行打一个断点,代码运行到此处后,按住control键 点击 step into, 接下来,就来到里这里

WX20210605-214413@2x.png 我们可以看到一个 objc_alloc 的函数方法到调用,此时,我们再下一个符号断点,同样的,我们还是找到了 libobjc.A.dylib 这个库。

WX20210605-215027@2x.png

方法3

此外,我们还是可以通过汇编来调试和查找相应的实现内容,断点依然是在alloc那一行。

Debug > Debug Workflow > Always Show Disassembly

WX20210605-215336@2x.png

找到 callq 方法调用那一行, WX20210605-215715@2x.png

接着, step into 进去, 我们找到了 objc_alloc 的调用, 之后的操作和 方法2的后续步骤一样,最终,可以找到 libobjc.A.dylib 这个库。 WX20210605-215732@2x.png

深探

下载源码 objc4-818.2

接下来对源码进行分析,

alloc方法会调用到此处

WX20210605-231454@2x.png

接着是 调用 _objc_rootAlloc

WX20210605-231517@2x.png

之后调用 到 callAlloc

WX20210605-231545@2x.png

跟着断点会来到 _objc_rootAllocWithZone

WX20210605-231647@2x.png

之后是 _class_createInstanceFromZone

此方法是重点

WX20210605-231758@2x.png

_class_createInstanceFromZone 方法中,该方法就是一个类初始化所走的流程,重点的地方有三处

第一处是:
    // 计算出开辟内存空间大小
size = cls->instanceSize(extraBytes);

内部实现如下: WX20210605-231838@2x.png 其中在计算内存空间大小时,会调用 cache.fastInstanceSize(extraBytes) 方法,

最终会调用 align16(size + extra - FAST_CACHE_ALLOC_DELTA16) 方法。 align16 的实现如下:

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

可见, 系统会进行 16字节 的对齐操作,也就是说,一个对象所占用的内存大小至少是16字节。

在这里 我们举个例子: size_t x = 8; 那么 align16操作后的大小计算过程如下:

    (8 + 15) & ~15;

0000 0000 0000 1000 8
0000 0000 0000 1111 15

= 0000 0000 0001 0111 23
1111 1111 1111 0000 ~15

= 0000 0000 0001 0000 16


第二处是:
    ///向系统申请开辟内存空间,返回地址指针;
obj = (id)calloc(1, size);

第三处是:
    /// 将类和指针做绑定
obj->initInstanceIsa(cls, hasCxxDtor);

总结:

所以,最后我们总结一下, alloc的底层调用流程如下:

alloc流程.001.jpeg

就是这样一个流程,系统就帮我们创建出来一个类对象。

补充

image.png

  • lldb 如何打印实力对象中成员为 double 类型的数值: e -f f -- <值>

0 个评论

要回复文章请先登录注册