这是我参与更文挑战的第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的概率很大 - 这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降