今天的主题就是研究alloc和init,大家平时大多都是这样写的 [XXX alloc] init],那么我们从何入手呢?当然就是这里面的alloc。开篇之前先介绍下如何跟踪调试的小技巧
一: 跟踪调试技巧
1.1 断点 control + step into

objc_alloc 方法的实现在 libobjc.A.dylib 这个动态库中。

1.2 符号断点
既然是alloc,那一定会走alloc这个方法的
当断点走到WYPerson *personA = [WYPerson alloc];我们下一个符号断点


alloc内部就是调用了rootAlloc

1.3 汇编
用真机进行调试
去掉符号断点,走到WYPerson *personA = [WYPerson alloc];
选择Xcode菜单中 Debug - Debug Workflow - Always Show Disassembly


我们再objc_alloc打个断点之后往下走,走到这


延伸:
register read 可以读取寄存器
二:alloc
2.1 alloc
alloc方法点进去,一脸懵逼,并没有具体的实现,我们知道alloc会申请内存并返回,那我想知道alloc做了什么东西,我该怎么做呢?
大家参考这个objc4-750编译,打开工程之后我们直接还是新建一个WYPerson的类,在main.m 初始化一个对象并且打印,发现的确返回了一个对象,那alloc到底做了什么呢,我们来跟一下代码
alloc中会调用 _objc_rootAlloc

但是,但是真的如此吗?来,见证奇迹的时刻!
通过之前介绍的跟踪代码的技巧我们发现会走到objc_alloc

_objc_rootAlloc方法呀,我们来跟源码看下。找到objc_alloc方法,打上断点,我们静静的等待


我们看到的确到了objc_alloc,经过多次验证,这个方法只走一次,在这里又调用了callAlloc,就跟下面介绍的在_objc_rootAlloc中调用 callAlloc一样了,但是问题在于为什么就走一遍呢?在源码里面找到这么一段,个人猜想在编译阶段,判断alloc,然后进行符号绑定,将IMP指向objc_alloc,这些都是底层处理的,而第二次走alloc方法才是正常走的消息转发!

_objc_rootAlloc中会调用 callAlloc

这里贴上callAlloc的代码,并加上相应的注释
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
/**
fastpath(x)表示x很可能不为0,希望编译器进行优化;
slowpath(x)表示x很可能为0,希望编译器进行优化——这里表示cls大概率是有值的,编译器可以不用每次都读取 return nil 指令
*/
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
/*
实际意义是hasCustomAllocWithZone——这里表示有没有alloc / allocWithZone的实现(只有不是继承NSObject/NSProxy的类才为true)
*/
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
/*
内部调用了bits.canAllocFast后分为2种情况
if(FAST_ALLOC){
}else{
默认为false,
}
当我们查看FAST_ALLOC宏时,发现它的上方有个else 1 之后才是else中定义这个宏,
也就是说这个宏一直是不存在的
可以自行点击进入查看
*/
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
通过断点我们发现,fastpath(cls->canAllocFast())是不会走的,最终会来到class_createInstance的方法。从方法名上大概能看出这个方法就是是创建一个实例的,而它确实返回了一个对象给我们,下面我们来对class_createInstance进行相关的探究

2.2 class_createInstance方法探究
进入class_createInstance方法内部,发现它调用了一个_class_createInstanceFromZone(cls, extraBytes, nil)方法
id
class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
我们继续进入_class_createInstanceFromZone(cls, extraBytes, nil)方法内部,并贴上关键代码

obj = (id)calloc(1, size);这句走完的时候,的确返回给我们一个地址,此时还不是我们要的对象,有意思的是这个size是16字节,一会下面会说到。而当obj->initInstanceIsa(cls, hasCxxDtor);这句走完的时候,obj神奇的就变成了WYPerson对象了,其中initInstanceIsa这个方法就是初始化了isa,并进行了关联。
2.3 一个对象的大小占多少内存空间
曾经面试的时候被问过这个问题,当时答的是16字节,别人问为什么就答不上来了。我们回到_class_createInstanceFromZone方法中的这句代码,的确size就是16

instanceSize方法里面看看
size_t instanceSize(size_t extraBytes) {
// alignedInstanceSize 对齐当前的实例
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
// 对象的大小一定是8的倍数,当小于8的时候z,直接返回16个字节
if (size < 16) size = 16;
return size;
}
测试创建的WYPerson并没有添加任何东西,这个8其实是isa的大小

alignedInstanceSize,其中word_align内部是会进行位运算,unalignedInstanceSize()代表的是未对齐的内容
uint32_t alignedInstanceSize() {
// word_align 字节对齐
return word_align(unalignedInstanceSize()); // 这句话的意思就是没有字节对齐的内容进行字节对齐然后返回
}
unalignedInstanceSize()
uint32_t unalignedInstanceSize() {
assert(isRealized());
// data 是dyld加载的数据段,其实就是当前类的信息
// ro就是ro_t 编译期确定的一些属性,方法,协议
return data()->ro->instanceSize;
}
结论:对于对象所占的内存,会进行内存对齐,不足16的,返回16个字节,这是一种以空间换时间的策略,所以一个对象的大小至少是16字节。
2.4 如何查看内存
2.4.1 lldb
我们在对象内部添加两个个属性

x 对象,我们这里就是 x p2

x/4xg 对象,我们这里就是x/4xg p2 ,读取从前往后的多少段内存,这里读取的是4段

2.4.2 Debug - Debug Workflow - View Memory

三 init
其实init才是最简单的,内部实现什么都没有做,仅仅是返回对象,这是工厂设计模式的一种体现,给我们去重写init,做一些初始化工作。

我们常常这么写
- (instancetype)init{
if (self = [super init]) {
//一些当前类的初始化操作
}
return self;
}
四 alloc流程图
