大拿之路,任重道远,永不言弃!!!
一.学习线索
我们平时的开发中都会认为main函数是应用程序的入口,是否是这样的?我们在main函数的入口添加一个断点。bt查看运行的堆栈信息,发现一些端倪!

发现在main函数之前还有一个start,并且这个start来自动态库libdyld.dylid。说明main函数并不是应用程序的入口,在此之前还有一些应用程序启动过程。
很陌生,我们先从简单的开始,在这里添加一个符号断点alloc,过掉main函数入口的断点,bt查看堆栈信息,可以发现更多程序加载内容。

比如UIKitCore、CoreFoundation、libdispach.dylib等等,我们开发过程中最常用的比如UI、NSObject、GCD等内容,均是来自这些库。
先从最熟悉的创建对象开始!!!
二.alloc原理初探
开发过程中创建对象是我们最常写的代码。
GFPerson * gf = [[GFPerson alloc] init];
留下一个问题,这里alloc做了什么,init又做了什么呢?
1.探索案例
引入一个案例,使用alloc方法初始化一个对象gf1,通过gf1调用init方法创建了gf2和gf3,然后分别输出对象、对象地址、指针地址。

根据运行结果,发现三者的对象输出是一样的,对象地址也是一样的,指针地址不一样。
案例总结:
- 三个指针指向了同一个对象,这个对象是
<GFPerson: 0x600000fbc1f0>; - 三个指针的地址是
连续的存储在栈区中,并且从高位向低位开辟内存空间;

指针占用空间是8个字节;lg1通过alloc方法开辟了内存空间,创建了对象;通过init方法初始化的lg2和lg3并没有开辟内存空间,也就是没有创建对象。
那么,alloc是如何开辟内存空间创建对象的呢?init又起到什么作用呢?
2.底层探究方法
在我们的开发工程中,Jump to Definition只能看到alloc方法的声明,不能看到真正的源码实现流程。
+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
复制代码
下面提供三种底层探究的方法。
1. 添加符号断点的形式直接跟流程
我们要研究alloc方法,那就添加个符号断点alloc。符号断点添加方式见下图:

因为我们暂时只研究GFPerson类的初始化流程,所以避免其他类调用alloc导致的干扰,运行时先将alloc断点设置为Disable Breakpoint。在程序运行到GFPerson * gf1 = [GFPerson alloc];时,再将alloc断点设置为Enable Breakpoint 。运行结果见下图:

根据上图的内容发现,[GFPerson alloc]最终走到了+[NSObject alloc];为什么是NSObject呢?因为LGPerson继承自NSObject,子类中没有alloc方法,最终会调用父类的alloc方法。
并且我们还发现了一个重要线索,[NSObject alloc]来自于libobjc.A.dylib动态库。
2. 通过按住control – step into
在GFPerson * gf1 = [GFPerson alloc];这行代码添加断点,运行程序,运行到断点后,点击control - step into。操作流程见下图:

进入后发现调用了objc_alloc,接着采用方式1添加符号断点objc_alloc,运行程序。

同样我们也发现了一个重要线索,objc_alloc也来自于libobjc.A.dylib动态库。
3. 汇编查看跟流程
设置方式:Debug -> Debug Workflow -> Always Show Disassembly。

在GFPerson * gf1 = [GFPerson alloc];这行代码添加断点,运行程序。进入汇编流程,见下图:

看不懂汇编,但是根据关键字眼也是可以发现一些端倪的,比如同样会走到objc_alloc方法,继续采用符号断点方式查找出处。

4.探索总结

根据上面的探索方式我们找到了alloc方法的出处,也就是动态库:libobjc.A.dylib。
同时也有个疑问,初始化调用了alloc方法,但在上面的汇编跟踪发现会调用objc_alloc方法,这两个方法又有什么关系呢?
如果要深入学习alloc的实现流程,下载源码objc4源码。源码下载地址:objc4源码下载地址
三.结合源码分析alloc
源码下载完成,采用关键字alloc {全局搜索,在NSObject.mm文件(Objective-C++汇编)中找到alloc的方法实现。
1.alloc的流程分析
根据上面搜索的alloc方法实现,跟踪关键源码如下:
// alloc
+ (id)alloc {
return _objc_rootAlloc(self);
}
// _objc_rootAlloc
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
// callAlloc
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())) {
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));
}
// _objc_rootAllocWithZone
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);
}
复制代码
从源码可以得到如下流程:

如何验证callAlloc走的是_objc_rootAllocWithZone还是objc_msgSend呢?没错上面已经学习了探索方式,这里可以使用汇编+添加符合断点!
下面设置符号断点,在程序运行到GFPerson * gf1 = [GFPerson alloc];之后再将符号断点设置为Enable Breakpoint,确保跟踪的是GFPerson的初始化流程。断点调试,发现走的是_objc_rootAllocWithZone的流程。

继续跟踪源码,_objc_rootAllocWithZone ->class_createInstanceFromZone,那么alloc的流程见下图:

2.流程梳理
alloc的流程跟踪完成,貌似没啥问题,但是上一章节中的疑问还没有解开!初始化调用了alloc方法,但在上面的汇编跟踪发现会调用objc_alloc方法,这两个方法又有什么关系呢?
- 在
control - step into的方式中,跟踪代码,走的是objc_alloc方法。 - 汇编查看跟踪方式中,也是调用了
objc_alloc方法。
问题出在哪?既然有源码了,设置断点,跟踪一下!分别在alloc方法和objc_alloc方法中设置断点。看看它到底会走到哪里!设置方式见下图:

同样的处理方式,当程序走到GFPerson * gf1 = [GFPerson alloc];之后再将alloc方法和objc_alloc方法两处断点设置为Enable Breakpoint,确保跟踪的是GFPerson的初始化流程。运行代码:

神奇的事情发生了,调用的alloc方法,结果运行进入了objc_alloc方法中。断点调试,跟踪发现,会调用callAlloc的objc_msgSend方法,发送一个alloc消息。

重新梳理一下流程,alloc流程就是下面这样子:

这个流程有些长,还需要再梳理一下,关键点在callAlloc方法,calloAlloc到底会走哪个分支需要深入研究一下!关键代码是:
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())) {
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));
}
bool hasCustomAWZ() const
{
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
复制代码
代码解读:
- 判断缓存中是否存在自定义的
alloc/allocWithZone地方实现,显然第一次运行类中是没有该方法缓存的。 - cls是什么?类嘛?不是!看看源码的定义:
typedef struct objc_class *Class;。所以cls是一个指针,指向一个结构体,这个结构体也就是Core Foundation层的类! - 类的初始化在
read_images方法执行时,而实例对象的初始化在alloc的时候。 - 第一次执行
((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));,会进行慢速方法查找,找到NSObject类的alloc方法,并将方法放入方法缓存。 - 所以除了第一调用
alloc方法外,之后在进行对象初始化会直接走_objc_rootAllocWithZone方法。可进行debug跟踪验证!已验证!
综上,得出以下流程图:

至此基本摸清了alloc的调用流程,但是问题是**调用alloc方法为什么会走到objc_alloc中呢?** —— llvm!后面再解密!
三.Alloc核⼼⽅法
_class_createInstanceFromZone方法核心流程见下图:

1.cls->instanceSize
此流程会计算出需要的内存空间⼤⼩。跟踪cls->instanceSize源码是实现:

最终会进入align16方法,这个方法是16字节对齐算法。

流程解析:
- 初始化的原始内存
8 + 15 = 23,23二进制位:0001 0111; - 15二进制位:
0000 1111,取反操作得到:1111 0000; - 然后将1、2中得到的数据进行&运算,同时为1才得1,结果为:
0001 0000 = 16;
为什么需要16字节对齐?有以下几点:
- cpu在存取数据时,并不是以字节为单位,而是以块为单位存取。频繁存取字节未对齐的数据,会极大降低cpu的性能,所以通过减少存取次数来降低cpu的开销;
16字节对齐,是由于在一个对象中,第一个属性isa占8字节(继承自父类),当然一个对象肯定还有其他属性,当无属性时,会预留8字节,即16字节对齐,如果不预留,相当于这个对象的isa和其他对象的isa紧挨着,容易造成访问混乱;16字节对齐后,可以加快CPU读取速度,同时使访问更安全,不会产生访问混乱的情况。
2.calloc
向系统申请开辟内存,返回地址指针。此流程会临时分配一个脏内存,调用calloc后分配的内存空间才是创建对象的内存地址。

3.obj->initInstanceIsa
关联到相应的类,即将开辟的内存空间指向所要关联的类!通过运行结果发现,在调用obj->initInstanceIsa之前,obj只有一个内存地址,而调用之后明确了对象类型为LGPerson。

四.补充内容
1.编译器优化
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
fastpath:可以理解为快速流程,对更有可能执行的流程进行优化,调高运行速度;
slowpath:基本流程,不被优化的。

一般针对发布版本设置为fastest, Smallest,让发布版本更快更小。对开发版本设置为None,便于调试!
2.内存优化
如果自定义类没有定义属性,仅仅只是继承自NSObject,则这个实例对象实际占用的大小为8字节,使用的为8字节对齐算法。系统实际分配的内存大小,使用的为16字节对齐的算法。
内存对齐可以理解为结构体中的成员申请内存大小时,系统最小分配的内存为8字节,是按每次8字节来申请的,不够8字节的也会申请8字节,然后按结构体中成员变量顺序再次申请,直到所有成员变量都能放下为止。
- 内存优化(属性重排)
当结构体成员内存小的在前面时会因为内存对齐的原因比较浪费内存,为了解决这个问题苹果中采用空间换时间,将类中的属性进行重排,以此来达到内存优化的目的。

init new等内容待补充……























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)