这是我参与更文挑战的第3天,活动详情查看: 更文挑战 [TOP]
前言
iOS攻城狮应该都对alloc
比较熟悉,都知道是用来初始化对象的,但alloc
方法是到底是如何来初始化对象的,很多人都卡在这里,因为在项目是看不底层实现的,现在我们来用探究一下
alloc对象的指针地址和内存
WLWPerson *p1 = [LGPerson alloc];
WLWPerson *p2 = [p1 init];
WLWPerson *p3 = [p1 init];
NSLog(@"%@-%p-%p",p1,p1,&p1);
NSLog(@"%@-%p-%p",p2,p2,&p2);
NSLog(@"%@-%p-%p",p3,p3,&p3);
打印
<WLWPerson: 0x600002cd0350>-0x600002cd0350-0x7ffee4b381c8
<WLWPerson: 0x600002cd0350>-0x600002cd0350-0x7ffee4b381c0
<WLWPerson: 0x600002cd0350>-0x600002cd0350-0x7ffee4b381b8
- 我们都知道
alloc
方法可是创建一个对象,这样就有了p1
指针,并分配了内存给这个对象 - 打印可以看出
p1, p2, p3
的内存地址是一样的,并且还是同一个对象, WLWPerson
对象是以0x6
开头在堆
上&p1,&p2,&p3
的指针地址不一样,0x7ffee4b381c8, 0x7ffee4b381c0, 0x7ffee4b381b8
连续排列相隔8字节
,&p1,&p2,&p3
都是以0x7
开头,是在栈
上,指向同一片内存空间<LGPerson: 0x600002cd0350>
汇编
通过查看汇编代码可以找到对应的符号
方法
打开汇编调试,然后在alloc
方法打上断点
在汇编中找到objc_alloc
符号
然后加上对应的符号断点
然后跳到对应的符号断点
这里我们就可以看到alloc
所在底层库libobjc.A.dylib
step into
按住control
键,再点这个
也可以找到objc_alloc
alloc 底层流程
打开源码搜索alloc {
,看到alloc
底层调用了 _objc_rootAlloc
方法
_objc_rootAlloc
callAlloc
- 最新的都是使用的都
__OBJC2
, checkNil
为发false
,因为必须要有class
,不需要检查slowpath(checkNil && !cls)
不是这个为false
的概率很大fastpath(!cls->ISA()->hasCustomAWZ())
为true
打概率很大cls->ISA()
其实就是获取元类hasCustomAWZ()
检测有没有allocWithZone
,NSObject
默认就有
_objc_rootAllocWithZone
class_createInstanceFromZone
- 这地方有三个非常重要的函数
instanceSize
计算对象需要分配的内存大小calloc
分配内存initIsa
对象的isa
与类绑定
instanceSize
-
大概率会走缓存
cach.fastInstanceSize
, -
字节对齐算法
x + 7 & ~7
-
字节对齐算法,其实就是
8字节对齐就是后三位抹零,就是8的倍数
,也可以通过左右移动来得 到(size + 7) >> 3 << 3
size = 10
10 + 7 = 17
0001 0001 (>> 3) -> 000 0010 (<< 3) -> 0001 0000
calloc
calloc
分配内存在libmalloc
源码里,需要进步进行分析
然后就是通过initIsa
函数,初始化isa
initIsa
然后将类与isa
进行绑定
isa
分为两种,一种是直接绑定了类,另一种是nonponterIsa
,因为isa
指针占用8位
也就是64bit
,避免浪费,所有isa
里存了类的地址外,还存有其他,这个需要进一步研究
这样我们就在开辟了一个内存来存储我们的对象了。
init, new 做了什么
init
在底层又做了什么呢
通过源码可以看出,init
什么也没做,直接返回了self
,所以inti
只是一种设计模式,为了方便开发者重写和在初始化的时候做一些事情
那new
跟alloc init
有什么区别呢
可以看出new
其实就相当于alloc init
,为了方便拓展传参还是尽量少使用new
来初始化对象
总结
言不胜图,下面为alloc
流程图
拓展
为什么 callAlloc 调用了两次
- 调用
alloc
,先调用的是objc_alloc
,而不是_objc_rootAlloc
, 汇编那里也看到了,而且会调用两次callAlloc
,这是因为调用alloc
,在进行消息转发的时候,llvm
会将所有的alloc
,被修正到了objc_alloc
下面是llvm
源码中的体现,可以看出当版本号大于8.0
就会调用objc_alloc
为什么[NSOBject alloc] 时候 callAlloc
只调用了一次
-
因为在项目启动时
NSObjec
类就已经调用了一次 -
还有就是
NSObject
的objc_alloc
会在程序启动时调用一次,所以他和其他的类不太一样不会调用俩次callAlloc
slowpath(x) 和 fastpath(x)
#define fastpath(x) (__builtin_expect(bool(x), 1)) 为false的概率比较大
#define slowpath(x) (__builtin_expect(bool(x), 0)) 为true的概率比较大
- 这个指令是gcc引入的,作用是允许程序员将最有可能执行的分支告诉编译器。这个指令的写法为:
__builtin_expect(EXP, N)
。 意思是:EXP==N
的概率很大 - 这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降