
欢迎大家继续阅读iOS原理探索系列篇章(后续陆续完善补充)
1.探索背景
在序章中,初步探索了iOS程序启动流程,但是很多东西都涉及系统底层库的加载,以及一系列晦涩难懂的资料,所以为了由浅入深的总结梳理,我这里就从我们最熟悉的陌生人对象开始。
作为程序猿,没对象不可怕,可怕的是我们居然只能自己new一个对象出来,我们新建对象的方式有两种,如下:

2.探索方式
想要知道对象创建方法在底层到底做了哪些事情,我们就需要去看源代码的实现,正常我们使用的方式就是跳转到方法定义去查看实现,如下图所示,我们最多能到NSObject的方法声明,无法去查看具体的源代码了,但是我们还是有其他的方式可以去查看源码实现。

3.三种源码调教手段
-
小断点调试
正常我们打完断点如下图:
下面我们按住
control键,再看断点,可以看到出现了变化如下图: 接下来我们按住
control的同时,点击中间的下箭头,可以看到程序来到汇编: 我们可以知道,在我们调用
alloc方法时,系统底层调用了objc_alloc,所以我们可以添加符号断点objc_alloc查看底层库
可以看到
alloc底层库是libobjc.A.dylib库。
-
符号断点调试 同样我们可以直接添加符号断点
alloc来查看底层库实现,如下图: 然后我们就可以看到出现了非常多的
alloc的调用
同样可以看到底层是
libobjc.A.dylib库实现。 -
开启
Debug Workflow模式 通过前两种方式我们可以看到,底层都是汇编,因为OC是高级语言,最终能被机器识别的还是汇编语言,所以我们可以直接开启查看汇编代码模式,如下图: 开启后,可以看到如下效果,可以看到汇编代码中同样出现了
objc_alloc 然后我们按照方式一按住
control+下箭头,继续进入到objc_alloc的断点,继续往下走,看到如下,同样能够看到objc_alloc
4. libobjc.A.dylib源码分析
Apple其实开源了很多源码给我们,libobjc也是其中之一,想要直接查看源码实现可以按照如下操作:
- 官方objc源码下载,最新版本是
objc4-779.1- 官方源码调试编译请查看Cooci老司机的这篇文章最新macOS 10.15下objc4-779.1源码编译调试
这里我也下载编译了最新的779.1,然后我们可以查看源代码,但是我们可以看到源代码非常多,大部分人一开始看肯定很懵逼,所以我们不用全部文件去看,只需要看我们的目标代码即可。

就如同我们在断点调试中已经知道, alloc方法最终底层都是到NSObject的alloc方法,所以我们可以直接找到NSObject的头文件,查看alloc方法的实现,也可以全局搜索alloc {,如下图:

接下来我们就可以逐步去查看alloc的底层实现原理了。
5.alloc流程分析
-
我们查看
_objc_rootAlloc的具体实现可以看到如下代码,其实从注释可以很清晰的看到,所有创建类的初始化方法最终都是调用了callAlloc函数。
-
接下里我们来具体分析下
callAlloc函数到底做了做什么,如下,由于我们目前基本上都是在objctive-c 2.0环境下,所以只需要看__OBJC2__这个判断下的代码即可。
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())) {
// 这是判断一个类是否有自定义的 +allocWithZone 实现。
//hasCustomAWZ : hasCustomAllocWithZone
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));
}
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);
}
- 最终来到实现函数
_class_createInstanceFromZone,在实现函数中我们可以看到,我们先为obj申请分配了内存空间,并且绑定地址
static ALWAYS_INLINE id
_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());
//hasCxxCtor() 是判断当前 class 或者 superclass 是否有 .cxx_construct 构造方法的实现。
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
//hasCxxDtor() 是判断判断当前 class 或者 superclass 是否有 .cxx_destruct 析构方法的实现。
bool hasCxxDtor = cls->hasCxxDtor();
//具体标记某个类是否支持优化的isa.
bool fast = cls->canAllocNonpointer();
size_t size;
//获取类的大小 (传入额外字节的大小)
size = cls->instanceSize(extraBytes);
//如果传入分配大小就需要修改
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
/**
* void *calloc(size_t __count, size_t __size)
* 在内存的动态存储区中分配 __count 个长度为 __size 的连续空间
*/
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
// 对象isa的初始化 以及绑定 内存空间
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);
} 
6.alloc原理分析
- 关于
_objc_rootAlloc,我们通过代码调试发现,这里alloc点击进入的为_objc_rootAlloc其实我们断点后发现进入的是objc_alloc。(关于这个原因,我后面会单独分析下,这里暂时不展开,因为最终大家调用的都是callAlloc这个函数)
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
// 这里传入了一个`Class`参数
typedef struct objc_class *Class;
// 我们看下objc_class的结构
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
...
// 再看看objc_object的结构
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
} OBJC2_UNAVAILABLE;
由于我们新建的是一个objc_class类,objc_class又继承于objc_class,objc_class存储了isa的一个Class结构体,指向当前是什么类。
-
关于
callAlloc函数if (slowpath(checkNil && !cls)) return nil;判定当前类是否存在
//__builtin_expect 常用于 if-else 的判断为了优化判断的速度。 //bool(x) 为真的可能性更大,if 下的代码执行的可能性更高 #define fastpath(x) (__builtin_expect(bool(x), 1)) //bool(x) 为假的可能性更大,else 下的代码执行的可能性更高 #define slowpath(x) (__builtin_expect(bool(x), 0))-
if (fastpath(!cls->ISA()->hasCustomAWZ()))的判断
hasCustomAWZ其实就是hasCustomAllocWithZone的意思,所以这里用来判定继承了NSObject/NSProxy的类才会进入这里。 -
关于
_class_createInstanceFromZone函数 可以看到_objc_rootAllocWithZone里面最终调用的就是_class_createInstanceFromZone -
hasCxxCtor()bool hasCxxCtor = cls->hasCxxCtor(); hasCxxCtor()是判断当前class或者superclass是否有.cxx_construct构造方法的实现。 -
hasCxxDtor()bool hasCxxDtor = cls->hasCxxDtor(); hasCxxDtor()是判断判断当前class或者superclass是否有.cxx_destruct析构方法的实现。 -
canAllocNonpointer()bool fast = cls->canAllocNonpointer();具体标记某个类是否支持优化的isa. -
instanceSize()
size_t size = cls->instanceSize(extraBytes); 获取类的大小(传入额外字节的大小) size_t instanceSize(size_t extraBytes) const { if (fastpath(cache.hasFastInstanceSize(extraBytes))) { // 判断是否有缓存 return cache.fastInstanceSize(extraBytes); } size_t size = alignedInstanceSize() + extraBytes; // 内存对齐 // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; return size; }-
内存对齐(
alignedInstanceSize()) 在unalignedInstanceSize方法中data()->ro->instanceSize获取类所占用空间的大小,其实是在MacO的data段的ro中的获取类所占用的大小。关于字节对齐
OC是8字节对齐,在word_align这个方法中计算了字节对齐。
static inline uint32_t word_align(uint32_t x) { //字节对齐 return (x + WORD_MASK) & ~WORD_MASK; }- 关于对齐算法,可以参考下面的示例说明:如下计算过程就能看到 OC 采用了 8 字节对齐,可以看到我们创建一个空类的大小是8字节(只有一个ISA指针),但是苹果对于类做了限制,最小需要16字节。
假设传入的参数: x = 9 x + WORD_MASK = 9 + 7 = 16 WORD_MASK 二进制 :0000 0111 = 7(4+2+1) ~WORD_MASK : 1111 1000 16二进制为 : 0001 0000 1111 1000 0001 0000 ········· 0001 0000 = 16 所以 x = 16(原始值:9) 也就是 8的倍数对齐,即 8 字节对齐 也可以用类似位运算来实现此算法 比如 (x >> 3) <<3 也可以实现对等功能initInstanceIsa()上一步我们获取到了obj的内存空间,接下来使用obj->initInstanceIsa(cls, hasCxxDtor)绑定isa指针,说明这块内存空间是为谁开辟的。 然后返回obj。
7.关于new和init
init方法,如下,可以看到其实init方法什么都没做,只是返回了对象本身
// Replaced by CF (throws an NSException)
+ (id)init {
return (id)self;
}
- (id)init {
return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function.
// Many classes do not properly chain -init calls.
return obj;
}
new其实最终调用了objc_opt_new,本质上就相当于[[cls alloc] init]
id
objc_opt_new(Class cls)
{
#if __OBJC2__
if (fastpath(cls && !cls->ISA()->hasCustomCore())) {
return [callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/) init];
}
#endif
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(new));
}
8. 流程图总结

以上就是对alloc的初步总结,中间有些细节问题可能没有完全覆盖到,比如对象的析构函数,构造函数等,后面会在类的初始化分析中详细分析,敬请期待~