OC底层探索03-常用的alloc,init,new到底做了什么
2020-09-11 本文已影响0人
Henry________
前言:想必大家对于[xxx alloc] init]
非常熟悉了,都知道是创建一个xxx的对象,但是OC底层到底做了什么?
首先看下方代码:
HRTest * t = [HRTest alloc];
HRTest * tt = [t init];
HRTest * ttt = [t init];
HRTest * tttt = [HRTest alloc];
NSLog(@"%@---%p---%p",t,t,&t);
NSLog(@"%@---%p---%p",tt,tt,&tt);
NSLog(@"%@---%p---%p",ttt,ttt,&ttt);
NSLog(@"%@---%p---%p",tttt,tttt,&tttt);
-
输出结果:
常识:
- %p/t :是指向对象的指针 %p/&t :是指向对象指针的指针
- 栈区存放原则:从
高位到低位
; 堆区存放原则:从低位到高位
- 内存地址是连续的
- 根据观察得出若干结论:
- 经过
alloc
之后获得了不同的内存空间
,经过init
之后内存空间
相同。
推断:内存空间是由alloc负责申请,从这个角度看init并没处理任何动作 - 对象是存放在
堆区
; 对象的指针存放在栈区
- 经过
对象的存储位置
用一张图来解释:
alloc
alloc
想要一探alloc
是如何申请了内存空间的,就需要使用上篇中提到的objc源码了。废话不多说,打开源码,加上断点,一步步开始调试:
此处有两种可能,简述流程省略代码:
- 创建
NSObjec
直接进入alloc
流程 - 创建继承自NSObject的自定义类
先进入_objc_alloc
->callAlloc
->alloc
,为什么会进入_objc_alloc而不是调用的alloc这就要涉及到llvm
中的知识,后续有机会再来解释,可以简单理解为llvm做了一次类似于hook
的操作,将alloc
转为_objc_alloc
- 最终到达了
_class_createInstanceFromZone
,这个方法是正在来进行内存申请操作的地方
alloc流程图
_class_createInstanceFromZone
_class_createInstanceFromZone流程图init
//此处只放出最核心代码
_class_createInstanceFromZone(...){
...
size_t size;
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;
if (size < 16) size = 16;
return size;
}
size_t fastInstanceSize(size_t extra) const
{...
//计算实际内存占用
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
static inline size_t align16(size_t x) {
//著名的字节对齐算法
return (x + size_t(15)) & ~size_t(15);
}
calloc
//此处只放出最核心代码
_class_createInstanceFromZone(...){
...
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// 申请1块size大小的内存空间
obj = (id)calloc(1, size);
}
}
-
zone
方式:在iOS8以后就基本不使用了 - 注意
calloc
返回的是一个id
,表示当前并无类型
initInstanceIsa
//此处只放出最核心代码
_class_createInstanceFromZone(...){
...
id obj;
if (!zone && fast) {
//一般会进入此判断
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
obj->initIsa(cls);
}
}
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
...
//进行类型赋值
isa = isa_t((uintptr_t)cls);
}
看完全部流程是不是感觉到流程索然繁杂,但是本质并不复杂,[狗头]
核心步骤:计算内存大小 - 申请内存 - 进行类的关联
fastpath、slowpath
在源码中反复出现的这两个宏定义,我觉得有必要简单解释一下:
//x很可能为真, fastpath 可以简称为 真值判断
#define fastpath(x) (__builtin_expect(bool(x), 1))
//x很可能为假,slowpath 可以简称为 假值判断
#define slowpath(x) (__builtin_expect(bool(x), 0))
- 来源:__builtin_expect命令是由gcc引入的
- 目的:提醒编译器可以对此处代码进行编译优化,以减少指令跳转带来的性能消耗
- 作用:在编译过程中就允许程序员将最有可能执行到的代码分支告诉编译器
1,自定义类第一次callAlloc时没有找到默认的allocWithZone,经过objc_msgsend(alloc)之后,第二次callAlloc时找到了默认的allocWithZone。allocWithZone是什么时候创建加载的呢?
init做了什么
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj)
{
return obj;
}
- 事实上init方法并没有做任何事情,也应证之前的猜想:
内存空间是由alloc负责申请,从这个角度看init并没处理任何动作 - apple苹果这样设计的目的:类似工厂方法,为后续
自定义做一些处理提供一个入口
new做了什么
一般在开发中,初始化除了init,还会使用new
,通过源码来看两者本质上并没有什么区别
+ (id)new {
retur开callAlloc(self, false/*checkNil*/) init];
}
但是在一般的开发中,如果使用自定的类,这里并不建议使用new
,因为这里系统只会调用init方法,对于自定义的initWhitXXX
并不会调用。但是系统自己类大可放心使用.
-
initWhitCustom
并没有调用
参考资料:
fastpath slowpath
iOS 内存字节对齐