OC对象的创建
我们经常使用alloc
,init
,new
来创建对象,它们有什么区别呢
直接上代码
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [p1 init];
LGPerson *p3 = [p1 init];
NSLog(@"p1---%@ - %p - %p", p1, p1, &p1);
NSLog(@"p2---%@ - %p - %p", p2, p2, &p2);
NSLog(@"p3---%@ - %p - %p", p3, p3, &p3);
打印结果如下:
首先解释一下
-
%@
打印的是对象 -
%p
打印的指针地址 -
%p - &p
打印的是指向对象内存的指针地址
由此可见 - 第一列:
p1
,p2
,p3
相等,对象都是0x100b27a40
- 第二列:
p1
,p2
,p3
相等,对象的指针地址都是0x100b27a40
,指向同一内存空间,说明了我们创建对象其实只用alloc
就可以了,不用再init
。 - 第三列:
&p1
,&p2
,&p3
不相等, 打印的是指针地址,obj1
、obj2
、obj3
三个不同指针, 地址不同
alloc探索
为什么创建对象只用alloc,不用再init,就可以创建一个对象?那我们就基于objc4-838(macOS 12.3 Xcode 13.2)进行探索一下,看系统具体是怎样实现一个对象的创建的。
下载objc4-838之后打开工程,选择SXObjcDebug工程
打开main文件,点击[LGPerson alloc]方法
进入[NSObject alloc]方法
依次进去,并打上相应流程断点
好了,我们开始运行项目,需要注意的是运行项目之前需要把打得断点取消掉,因为main函数之前系统也会创建很多的对象,我们只需要研究[LGPerson alloc]这个函数的具体实现就可以了。
首先定位到[LGPerson alloc]这行代码,恢复刚打得断点。
具体的调用流程依次是这样的
我们发现先走了这段代码callAlloc里面的
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));给这个class发了方法为alloc的一个消息,
打开函数调用栈,发现有一个objc_alloc函数,很显然是之前没用调用[NSObject alloc]这个方法的
搜索源码直接打上个断点,再运行,发现首先运行了objc_alloc方法,然后依次执行
alloc
_objc_rootAlloc
callAlloc -> _objc_rootAllocWithZone
_objc_rootAllocWithZone->_class_createInstanceFromZone
_class_createInstanceFromZone
这样就总结出来了底层alloc的调用流程
使用符号断点探索alloc
添加符号断点,依次加入objc_alloc, callAlloc, [NSObject alloc],_objc_rootAlloc, _objc_rootAllocWithZone, _class_createInstanceFromZone等。注意main函数之前生成的对象也会走相应的方法,要先把这些断点disable,断点到LGPerson *p1 = [LGPerson alloc];
需要注意的是alloc方法是一个类方法,加断点的是需要把类名加上,如[NSObject alloc]
开跑,函数调用顺序如下
可以看到_class_creatInstanceFromZone方法内部是返回的是一个obj的也就是一个NSObject对象
init方法
打开源码,init函数返回来_objc_rootInit,_objc_rootInit返回obj,看得出来,init方法没有进行任何操作,这一点苹果用到了工厂模式,当我们需要自定义某个类时,可以通过重写init方法来进行自定义操作。
编译器优化
编译器优化说明
- -O0 关闭所有优化 代码空间大,执行效率低
- -O1 基本优化等级 编译器在不花费太多编译时间基础上,试图生成更快、更小的代码
- -O2 O1的升级版,推荐的优化级别 编译器试图提高代码性能,而不会增大体积和占用太多编译时间
- -O3 最危险的优化等级 会延长代码编译时间,生成更大体积、更耗内存的二进制文件,大大增加编译失败的几率和不可预知的程序行为,得不偿失
- -Og O1基础上,去掉了那些影响调试的优化 如果最终是为了调试程序,可以使用这个参数。不过光有这个参数也是不行的,这个参数只是告诉编译器,编译后的代码不要影响调试,但调试信息的生成还是靠 -g 参数的
- -Os O2基础上,进一步优化代码尺寸 去掉了那些会导致最终可执行程序增大的优化,如果想要更小的可执行程序,可选择这个参数。
- -Ofast 优化到破坏标准合规性的点(等效于-O3 -ffast-math ) 是在 -O3 的基础上,添加了一些非常规优化,这些优化是通过打破一些国际标准(比如一些数学函数的实现标准)来实现的,所以一般不推荐使用该参数。
举个例子
int sum (int a , int b) {
return a + b;
}
在main函数里面调用该sum函数,使用----分割sum函数调用前后。
运行打开汇编模式,我们会看到3和4的值
打开Build Setting在Debug模式下系统默认是O0是不优化的
修改Debug和Release模式的一样的优化等级Os
再运行一次,发现编译器把sum函数也优化掉了
对象的内存对齐方式
alloc探索时 _class_createInstanceFromZone返回一个NSObject对象,我们看到这里面有一个instanceSize函数,它是给对象计算需要的内存空间的。
cmd+instanceSize点击 依次进入
看到了这个内存对其算法,在64位的架构下WORD_MASK为7,在31位的架构下WORD_MASK为3。
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
在64位架构下,假设一个对象实际内存是10,即x=10,
x + WORD_MASK = 10 + 7 = 17 = 10001
~WORD_MASK = 17~7 = 11000
(x + WORD_MASK) & ~WORD_MASK = 10001 & 11000 = 10000 = 16
也可以
(x + WORD_MASK) >> 3 << 3 = 10001 >> 3 << 3 = 10 << 3 = 10000 = 16
所以这个对象实际需要的内存大小就是16,苹果是以8字节对齐来计算对象的内存大小的。这样就可以减少CPU的开销,以空间换取时间。
结构体内存对齐
原则:
- 数据成员对⻬规则:结构(struct)的第一个数据成员放
在offset为0的地方,以后每个数据成员存储的起始位置要从
该成员大小或者成员的子成员大小的整数倍开始(比如int为4
字节,则要从4的整数倍地址开始存储)。 - 结构体作为成员:如果一个结构里有某些结构体成员,则结
构体成员要从其内部最大元素大小的整数倍地址开始存
储.(struct a里存有struct b,b里有char,int ,double
等元素,那b应该从8的整数倍开始存储)。 -
收尾工作:结构体的总大小,也就是sizeof的结果必须是
其内部最大成员的整数倍,不足的要补⻬。
数据类型占的字节数:
举例:
double:8字节 从0位置开始需要8位,目前到7号位置
char:1字节 从8号位置开始存储,需要1字节,到8号位置
int:4字节 从9号位置开始存储,需要4字节,9不是4的倍数,所以从12号位置开始存储,到15位置
short:2字节 从16号位置开始存储,需要2字节,到17号位置
因为该结构体内的成员最大是double是字节,17不是8的倍数,所以需要32字节的内存。
struct LGStruct1 {
double a; // 8字节 [0 7] 从0到7
char b; // 1字节 [8]
int c; // 4字节 [12 13 14 15]
short d; // 2字节 [16 17] 所以需要 24字节的内存
}struct1;
验证一下,打印结果是24,正确
调整一下结构体内部成员的顺序
double:8字节 从0位置开始需要8位,目前到7号位置
int:4字节 从8号位置开始存储,需要4字节,到11位置
char:1字节 从12号位置开始存储,需要1字节,到12号位置
short:2字节 从13号位置开始存储,需要2字节,13不是2的倍数,所以从14号位置开始存储,到15位置
因为该结构体内的成员最大是double是字节,15不是8的倍数,所以需要16字节的内存
struct LGStruct2 {
double a; // 8字节 [0 7] 从0到7
int b; // 4字节 [8 9 10 11]
char c; // 1字节 [12]
short d; // 2字节 [14 15] 所以需要 16字节的内存
}struct2;
验证一下,打印结果是16,正确
结构体内嵌结构体,分析LGStruct3需要64字节
struct LGStruct3 {
double a; // 8字节 [0 7] 从0到7
int b; // 4字节 [8 9 10 11]
char c; // 1字节 [12]
short d; // 2字节 [14 15]
int e; // 4字节 [16 17 18 19]
struct LGStruct1 struct1; // 24字节 [24 47]
struct LGStruct2 struct2; // 16字节 [48 63]
}struct3;
验证一下,打印结果是64,perfect