YcPerson *p = [YcPerson alloc];
YcPerson *p1 = [p init];
YcPerson *p2 = [p init];
YcPerson *p3 = [p init];
NSLog(@"%@-----------%p,%p",p1,p1,&p1);
NSLog(@"%@-----------%p,%p",p2,p2,&p2);
NSLog(@"%@-----------%p,%p",p3,p3,&p3);
2022-04-18 09:50:45.925545+0800 objc[11276:5563625] <YcPerson: 0x10121d500>-----------0x10121d500,0x7ff7bfeff340
2022-04-18 09:50:45.926195+0800 objc[11276:5563625] <YcPerson: 0x10121d500>-----------0x10121d500,0x7ff7bfeff338
2022-04-18 09:50:45.926259+0800 objc[11276:5563625] <YcPerson: 0x10121d500>-----------0x10121d500,0x7ff7bfeff330
0X7:栈内存地址 0X6:堆内存地址
alloc做的事情:
alloc开辟了一块内存区域,指针p1,p2,p3,为栈地址连续的三个指针,指向这一块内存区域
查看实现:
- 符号断点:objc_alloc
- 通过查看汇编语言Debug->Debug Workflow->Always Show Disassembly
一些汇编命令:
b,bl,callq都是调用函数,b是简单的跳转,bl是带返回值的跳转
b,bl 是arm架构,callq是X86架构,M系列之后都改成了arm架构
ret 函数的返回 :;注释
mov 是将数据从一个位置(寄存器或内存中)复制到另一个位置(寄存器或内存中)
movb、movw、movl和movq;主要区别是传送数据的大小不同,分别是1、 2、 4和8字节
x86-64中有一条限制,前后两两操作数不能都指向内存位置
- 查看苹果开源源码: opensource.apple.com/releases/
opensource.apple.com/tarballs/
alloc 调用顺序
点击源码的方法跳转,得到的调用顺序:
alloc -> _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone -> _class_createInstanceFromZone
通过汇编验证alloc调用顺序:
分别给+alloc,callAlloc,_objc_rootAlloc 打断点,得出三者顺序:
callAlloc -> alloc -> _objc_rootAlloc -> callAlloc ->
在[Person alloc]添加断点后运行,开启汇编模式,可以看到:
先调用了 objc_alloc方法,然后control+单步调试显示如下:
在NSObject.mm 1914行找到objc_alloc方法:
注释也显示,class 调用alloc方法会调用这个objc_alloc方法,然后在objc_alloc方法里调用了callAlloc方法.
而之所以是objc_alloc方法是因为在源码的objc-runtime-new种有个fixupMessageRef方法:
里面定义了当方法名为alloc时,调用imp为objc_alloc方法地址.
所以,实际调用顺序为:
[NSObject alloc] -> objc_alloc -> callAlloc -> alloc -> _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone -> _class_createInstanceFromZone ->
这里callAlloc调用了两次的原因:
首先要知道一点:调用allocWithZone时,会在内部调用_objc_rootAllocWithZone.
第一次进入callAlloc:参数allocWithZone为NO,fastpath(!cls->ISA()->hasCustomAWZ())为判断class
或superClass是否实现了allocWithZone方法,此时类还没有初始化,判断不成立->通过msgSend走alloc方法.
第二次通过_objc_rootAlloc进入callAlloc:参数allocWithZone为YES,此时如果此时没有实现
allocWithZone方法,则判断为YES,rentun _objc_rootAllocWithZone,开辟内存,
如果实现了allocWithZone方法,判断为NO,下方的判断allocWithZone为YES,依然通过msgSend(allocWithZone)走
_objc_rootAllocWithZone方法,最终结果都是_class_createInstanceFromZone方法
对象内存对齐
_class_createInstanceFromZone里面有个方法instanceSize,作用为计算给创建的实例对象需要多大的内存空间,最小为16字节.对象内部以8字节对齐,8字节算法可以写成:
int func (int x) {
return (x + 7)/8*8 ;
}
位运算:
int func (int x) {
return (x + 7) >> 3<<3 ;
}
位运算可以扩展为: n字节对齐的算法都可以写成:
int func (int x) {
return (x + n) & ~n;
}
与对象内部不同,系统为对象分配内存时,以16字节对齐(以空间换时间).
对象内存大小由成员变量决定,与方法协议等无关.
// May be unaligned depending on class's ivars.
结构体内存对齐
对象的本质都是objc_object的结构体,其中包含着一个NSObject_IMPL的isa指针+成员变量的值
isa 也是一个objc_class的结构体,所以占8个字节.
objc_class继承于一个最原始的类型:objc_object
内存对齐规则:
关于其中数据类型占用内存:
- 32位:
char : 1个字节
char*(即指针变量): 4个字节(32位的地址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int : 4个字节 范围 -2147483648~2147483647
unsigned int : 4个字节
long : 4个字节 范围 和int一样
long long : 8个字节 范围 -9223372036854775808~9223372036854775807
unsigned long long : 8个字节 最大值:1844674407370955161
float : 4个字节
double : 8个字节
BOOL : 1个字节
- 64位
char : 1个字节
char*(即指针变量) : 8个字节
short int : 2个字节
int : 4个字节 范围 -2147483648~2147483647
unsigned int : 4个字节
long : 8个字节 范围 -9223372036854775808~9223372036854775807
long long : 8个字节 范围 -9223372036854775808~9223372036854775807
unsigned long long : 8个字节 最大值:1844674407370955161
float : 4个字节
double : 8个字节
BOOL : 1个字节
接下来几个例子:
struct Person1 {
double a; //8 [0 7]
char b; //1 [8]
int c;//4 [12 13 14 15]
short d;//2 [16 17] 8的整数倍,不足补齐->24
};
struct Person2 {
double a; //8 [0 7]
int b; //4 [8 9 10 11]
char c;//1 [12]
short d;//2 [14 15] 一共16
};
struct Person3 {
double a; //8 [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 Person1 per;// 24的整数倍,不足补齐->48
};
打印结果没毛病:
init做的事情:
init只是返回了obj本身,init是个工厂模式,存在只是为了让开发者重写,在里面做一些初始化操作
编译器的优化:
taget ->Build Setting ->Optimization Level,从None到Smallest,
通常Release使用Fastest[Os]等级
用户优化编译器,提高效率