「OC」源码学习——alloc与init的实现
「OC」源码学习——alloc与init的实现
前言
费劲千辛万苦终于项目给写完了,进入下一个阶段,源码的学习
alloc的调用顺序
我们在main函数之中打上断点,先运行

再在alloc之中的各个函数之中打上断点,在关键步骤上打上断点,我们可以,很容易总结出我们alloc源码的编译流程。
先说结论,编译的具体流程如下

流程探究
接下来我们在按照这个顺序进行断点调试,我们现在alloc与objc_alloc处打一个断点,运行程序,我们会发现我们的断电先停在的是objc_alloc内,查看网上的资料,这是因为LLVM 在编译阶段会将 alloc 符号替换为 objc_alloc 入口点。

接着继续步入,断点情况如下

进入alloc流程之中的核心callAlloc,对两个return打上断点,我们可以看到,自定义类在第一次进入callAlloc的时候,是会使用消息转发的方法,再去调用真正的alloc函数

至于为何需要走两次callAlloc的原因我们先按下不表,接着程序再次调用callAlloc,这次我们会发现断点打在了_objc_rootAllocWithZone之中

进入函数看看

这个函数内容过于复杂,等到下一篇文章再详细阐述,我们只要知道,这个程序是动态创建类实例的核心函数,用于分配内存并初始化实例的 isa 指针。
Objective-C 中自定义类的 alloc 方法调用流程涉及双重 callAlloc 的机制,其根源在于 类初始化延迟 和 运行时多态性设计。以下是该流程的核心原因及分步解析:
1. 首次调用 objc_alloc
- 入口:
id objc_alloc(Class cls)是运行时提供的全局分配函数。 - 目的:
- 检查类是否已初始化(
cls->isRealized())。 - 若未初始化,触发
class_initialize()完成类加载。
- 检查类是否已初始化(
2. 进入 callAlloc
- 参数:
checkNil=true,allocWithZone=false。 - 逻辑:
- 检查类是否存在(
slowpath(checkNil && !cls))。 - 若类支持优化路径(未重写
allocWithZone:),直接调用_objc_rootAllocWithZone。
- 检查类是否存在(
3. 动态派发 objc_msgSend
- 触发条件:若类重写了
allocWithZone:,需通过消息发送调用自定义逻辑。 - 结果:进入类的
+alloc方法,最终再次调用callAlloc。
4. 第二次 callAlloc
- 参数:
checkNil=false,allocWithZone=true。 - 逻辑:
- 直接调用
_objc_rootAllocWithZone,跳过安全检查。 - 最终通过
_class_createInstanceFromZone完成内存分配和isa绑定。
- 直接调用
两次进入的区别
我们现在在来看看两次进入callAlloc究竟有什么区别
第一次调用 callAlloc 时,参数 checkNil=true,在程序之中需要验证类是否存在。

第二次调用 callAlloc 时,参数 checkNil=false,跳过安全检查。

其实差别就在于,后面传进的两个布尔值,其实就是是否需要进行安全检查。在第一次进入callAlloc会做一次检查,确保对应的类不为空。
为何自定义类需要进入两次callAlloc
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{if (slowpath(checkNil && !cls)) return nil;if (fastpath(!cls->ISA()->hasCustomAWZ())) {return _objc_rootAllocWithZone(cls, nil);}// 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));
}
系统类(如 NSObject):
因为NSObject在编译时已完成isa指针的初始化。所以不需要经过两次callAlloc
只需要直接进入objc_alloc->callAlloc->_objc_rootAllocWithZone直接申请空间即可
自定义类:
-
第一次
callAlloc通过objc_alloc进入,目的是 检查类的初始化状态,类初次创建是没有默认的alloc/allocWithZone实现的所以继续向下执行进入到msgSend消息发送流程,为这个类进行初始化。 -
第二次
callAlloc,此时类已初始化完成,进入实际内存分配逻辑。 -
callAlloc的分支逻辑:if (fastpath(!cls->ISA()->hasCustomAWZ())) {return _objc_rootAllocWithZone(cls, nil); // 直接分配内存 } else {return objc_msgSend(cls, @selector(alloc)); // 通过消息发送调用自定义逻辑 }hasCustomAWZ():检测类是否重写了allocWithZone:方法。- 若未重写(默认情况),直接调用
_objc_rootAllocWithZone分配内存。 - 若重写,则通过
objc_msgSend触发动态派发,确保调用正确的alloc实现。
那么源码是如何查看缓存的呢,我们进入
hasCustomAWZ()看一下# define FAST_CACHE_HAS_DEFAULT_AWZ (1<<14) bool hasCustomAWZ() const {return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);}bool getBit(uint16_t flags) const {return _flags & flags;}FAST_CACHE_HAS_DEFAULT_AWZ宏定义表示 是否实现alloc/allocWithZone位域标识位为了证明是类没有初始化的问题才导致
callAlloc函数在类第一次被调用的时候被进入两次,我们可以通过在申请一次空间来尝试一下,断点和之前的一样。GGObject *obj = [GGObject alloc]; GGObject *obj2 = [GGObject alloc];
我们发现obj2的alloc调用顺序就和NSObject一样了。
objc_alloc->callAlloc->_objc_rootAllocWithZone但紧接着问题又来了,重复进入callAlloc的原因我们找到了,那为什么要设置这个流程,这个所谓的类初始化实在哪里开始的呢?
查看
hasCustomAWZ()的调用栈可以看到,有个名字为_objc_msgSend_uncached的方法,其实也很好理解,就是消息发送给了一个初始化信息的类之中,其实就是在消息转发这一步顺带把第一次调用的类进行初始化,然后在第二次进入hasCustomAWZ()就能进入_objc_rootAllocWithZone

双重 callAlloc 流程是 Objective-C 运行时与编译器协作的产物,其核心目的是 确保类初始化完成 和 兼容自定义内存分配逻辑。这一机制平衡了性能、灵活性与安全性,是 Objective-C 动态特性的典型体现。
init
其实点进去init, 很简单,就是把内容返回一次,如此设计的原因是为了让整个程序符合工厂模式的设计理念,我们可以通过简单的重新init的方法去实现较为复杂的其他操作。

总结
其实我是想讲整一个alloc的流程梳理完整再将内容发出,无奈一个完完整整的流程涉及的内容实在过多,有想要了解的可以看 OC对象底层内存开辟和实现(中)了解初始化类的完整流程
参考资料
iOS-底层原理 02:alloc & init & new 源码分析
iOS-底层原理 04:NSObject的alloc 源码分析
OC对象底层内存开辟和实现(中)
