源码探索的三种方法
符号断点
直接跟流程- 通过
按住control
+step into
- 汇编跟流程
1、符号断点
直接跟流程
- 以
alloc
为例,展示如何所在的源码库
查找到函数
-
选择断点
Symbolic Breakpoint
符号断点中输入 alloc
注意
(
在走到这部分断点之前,需要关闭上面新增的符号断点,原因是因为alloc的调用有很多,在此之前也有可能调用了系统的alloc,如果开启了就不能准确的定位到)
2、通过 按住control
+ step into
按住control
,点击 step into
⬇️键,执行到下图对应 objc_alloc
3、汇编跟流程
- 加断点在alloc
- xcode 工具栏 选择
Debug
-->Debug Workflow
-->Always Show Disassembly
,这个 选项表示 始终显示反汇编 ,即 通过汇编 跟流程
- 按住
control
,点击step into
⬇️键,执行到下图的callq ,对应 objc_alloc
- 按住
control
,点击step into
⬇️键,执行到下图对应 objc_alloc
alloc初探
- 下载 objc4-818.2 源码
先来看看一下有关alloc的3个变量 内存地址 和 指针地址 区别:
打印结果是3个对象的内容、内存地址、指针地址 唯一不同的是对象指针地址
源码分析
- 【第一步】首先根据
main
函数中的LGPerson
类的alloc
方法进入alloc方法的源码实现(即源码分析开始)
//alloc源码分析-第一步
+ (id)alloc {
return _objc_rootAlloc(self);
}
- 【第二步】跳转至
_objc_rootAlloc
的源码实现
//alloc源码分析-第二步
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)// alloc 源码 第三步
{
#if __OBJC2__ //有可用的编译器优化
/*
参考链接:https://www.jianshu.com/p/536824702ab6
*/
// checkNil 为false,!cls 也为false ,所以slowpath 为 false,假值判断不会走到if里面,即不会返回nil
if (slowpath(checkNil && !cls)) return nil;
//判断一个类是否有自定义的 +allocWithZone 实现,没有则走到if里面的实现
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));
}
如上所示,在calloc方法中,当我们无法确定实现走到哪步时,可以通过断点调试,判断执行走哪部分逻辑。这里是执行到_objc_rootAllocWithZone
其中fastpath
中的 cls->ISA()->hasCustomAWZ()
表示判断一个类是否有自定义的 +allocWithZone 实现
,这里通过断点调试,是没有自定义的实现,所以会执行到 if 里面的代码,即走到_objc_rootAllocWithZone
- 【第四步】跳转至
_objc_rootAllocWithZone
的源码实现
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)// alloc 源码 第四步
{
// allocWithZone under __OBJC2__ ignores the zone parameter
//zone 参数不再使用 类创建实例内存空间
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
alloc 核心操作
核心操作都位于calloc
方法中
计算需要开辟内存的大小的执行流程如下所示
- 1、跳转至
instanceSize
的源码实现
size_t instanceSize(size_t extraBytes) const {
//编译器快速计算内存大小
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
// 计算类中所有属性的大小 + 额外的字节数0
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
//如果size 小于 16,最小取16
if (size < 16) size = 16;
return size;
}
- 2、跳转至
fastInstanceSize
的源码实现,通过断点调试,会执行到align16
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
//Gcc的内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数EXP 的值是常数,函数返回 1,否则返回 0
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
//删除由setFastInstanceSize添加的FAST_CACHE_ALLOC_DELTA16 8个字节
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
- 3、跳转至
align16
的源码实现,这个方法是16字节对齐算法
//16字节对齐算法
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
- calloc:申请内存,返回地址指针
通过instanceSize
计算的内存大小,向内存中申请 大小 为 size的内存,并赋值给obj,因此 obj是指向内存地址的指针
obj = (id)calloc(1, size);
- obj->initInstanceIsa:类与isa关联
经过calloc可知,内存已经申请好了,类也已经出入进来了,接下来就需要将 类与 地址指针 即isa指针进行关联,其关联的流程图如下所示
总结
- 通过对
alloc
源码的分析,可以得知alloc的主要目的就是开辟内存
,而且开辟的内存需要使用16字节对齐算法
,现在开辟的内存的大小基本上都是16
的整数倍 - 开辟内存的核心步骤有3步:
计算 -- 申请 -- 关联