OC底层原理02-alloc 探索
本文将围绕 alloc 作用 和 作用过程进行探索.
一. alloc 作用
我们创建一个继承自 Person
类, 只是简单创建一个类而已, 按照下面代码创建三个对象并打印出相关内容.
1.创建对象
平时我们创建对象都是这样的 Person *p1 = [[Person alloc] init];
, 今天我们换个套路, 分开写, 看看 alloc
方法到底做了些什么?
Person *p1 = [Person alloc];
Person *p2 = [p1 init];
Person *p3 = [p1 init];
2. 对 p1
p2
p3
进行如下打印
NSLog(@"%@ -- %p -- %p",p1,p1,&p1);
NSLog(@"%@ -- %p -- %p",p2,p2,&p2);
NSLog(@"%@ -- %p -- %p",p3,p3,&p3);
-
打印结果
p1 p2 p3 全部内容
分析打印结果:
p1
p2
p3
的 %@
和 %p
打印结果相同, 说明p1
p2
p3
指向同一块内存空间, 这块内存空间的地址是 0x282d34ba0
; &p1
&p2
&p3
的值是不同的, 说明 p1
p2
p3
是三个不同的变量.
代码
Person *p1 = [Person alloc];
:alloc
给p1
开辟了内存空间
代码Person *p2 = [p1 init];
:init
将 p1 指向的对象的内存空间返回给p2
代码Person *p3 = [p1 init];
:init
将 p1 指向的对象的内存空间返回给p3
- 由此可知:
init
并没有修改alloc
申请的内存空间.
alloc
作用总结 :
alloc
的作用是开辟内存空间.
福利:
p1
p2
p3
是指针变量, 存储在内存的栈区, 每个地址是 8 个字节, 通过p1
p2
p3
的地址可以发现,0x16d979258
与0x16d979250
差值是 8 字节,0x16d979250
与0x16d979248
差值是 8 字节, 说明这三个变量的地址是从高到低连续的. 如果这里一块, 那里一块, 必然有一部分空间会被浪费掉, 对于计算机而言, 内存空间是非常富贵的资源.
栈内存是连续的, 从而避免了内存空间的浪费.
二. alloc 开辟空间的流程
一般情况我们想探索一个方法的执行流程, 都会点进去看方法的实现, 那么我们跳进去看看源码是怎么实现的.
跳转操作
alloc 声明
当我们来到 alloc
的声明时, 想要继续跟进方法实现, 发现根本不行, 原因很简单, 这是个系统方法, 苹果是不开源的, 并且连注释都没有.
那么我们如何进行探索呢 ?
关于源码探索方式可以看我这篇文章 Object-C底层原理01-源码探索跟踪的三种技巧
我们通过符号断点可以找到 alloc
方法所在的源码库为 libobjc.A.dylib
, 此处为什么是 NSObject 的 alloc
方法呢 ? 因为 Person 类什么方法也没实现, alloc
方法是继承自 NSObject.
我们知道 alloc
所在的库的名称之后应该怎么办呢 ? 我们可以去苹果开源库网站找到并下载libobjc.A.dylib
这个库. 然后进行调试就可以了,
查找源码的地址:
本次探索我们使用 objc4-781
的版本. 打开第二个地址, 搜索 objc4 进去下载 781 版本.
开始探索源码 :
- 进入
alloc
源码
+ (id)alloc {
return _objc_rootAlloc(self);
}
- 进入
_objc_rootAlloc
源码
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
- 进入
callAlloc
源码
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
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));
}
如上所示,在 calloc
源码中,已经有了分支情况处理, 当我们无法确定走哪种情况时,可以通过断点调试,判断下一步执行哪部分逻辑。通过调试发现代码会执行 _objc_rootAllocWithZone
这个方法.
注意 :
slowpath & fastpath
其中关于slowpath和fastpath这里需要简要说明下,这两个都是objc源码中定义的宏,其定义如下
//x很可能为真, fastpath 可以简称为 真值判断
#define fastpath(x) (__builtin_expect(bool(x), 1))
//x很可能为假,slowpath 可以简称为 假值判断
#define slowpath(x) (__builtin_expect(bool(x), 0))
其中的 __builtin_expect
指令是由 gcc 引入的,
1、目的:编译器可以对代码进行优化,以减少指令跳转带来的性能下降。即性能优化
2、作用:允许程序员将最有可能执行的分支告诉编译器。
3、指令的写法为:__builtin_expect(EXP, N)
。表示 EXP==N
的概率很大。
4、fastpath定义中 __builtin_expect((x),1)
表示 x
的值为真的可能性更大;即 执行 if
里面语句的机会更大
5、slowpath定义中的 __builtin_expect((x),0)
表示 x
的值为假的可能性更大。即执行 else
里面语句的机会更大
6、在日常的开发中,也可以通过设置来优化编译器,达到性能优化的目的,设置的路径为:Build Setting --> Optimization Level --> Debug -->
将 None
改为 fastest
或者 smallest
cls->ISA()->hasCustomAWZ()
其中 fastpath
中的 cls->ISA()->hasCustomAWZ()
表示判断一个类是否有自定义的 +allocWithZone
实现,这里通过断点调试,是没有自定义的实现,所以会执行到 _objc_rootAllocWithZone
- 进入
_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);
}
- 进入
_class_createInstanceFromZone
源码
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());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
// 1:要开辟多少内存
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// 2;怎么去申请内存
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
// 3: 关联
if (!zone && fast) {
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);
}
恭喜你进入了 alloc 核心源码, 主要分为三大部分
-
cls->instanceSize
:计算需要开辟的内存空间大小 -
calloc
:申请内存,返回地址指针 -
obj->initInstanceIsa
:将 类 与isa
关联
我们将源码分析结果绘制成流程图
alloc 执行流程图 :
alloc 核心方法 :