OC底层探索(一):alloc&init

351 阅读4分钟

这是我参与更文挑战的第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>

image.png

汇编

通过查看汇编代码可以找到对应的符号方法

打开汇编调试,然后在alloc方法打上断点

image.png

在汇编中找到objc_alloc 符号 image.png

然后加上对应的符号断点

image.png

然后跳到对应的符号断点

image.png

这里我们就可以看到alloc所在底层库libobjc.A.dylib

step into

按住control 键,再点这个 image.png

也可以找到objc_alloc

image.png

alloc 底层流程

打开源码搜索alloc { ,看到alloc底层调用了 _objc_rootAlloc 方法

_objc_rootAlloc

image.png

image.png

callAlloc

image.png

  • 最新的都是使用的都__OBJC2
  • checkNil为发false,因为必须要有class,不需要检查
  • slowpath(checkNil && !cls) 不是这个为false的概率很大
  • fastpath(!cls->ISA()->hasCustomAWZ())true打概率很大
  • cls->ISA() 其实就是获取元类
  • hasCustomAWZ() 检测有没有allocWithZone, NSObject默认就有

_objc_rootAllocWithZone

image.png

class_createInstanceFromZone

image.png

  • 这地方有三个非常重要的函数
  • instanceSize 计算对象需要分配的内存大小
  • calloc 分配内存
  • initIsa 对象的isa 与类绑定
instanceSize

image.png image.png image.png

  • 大概率会走缓存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

image.png

然后将类与isa进行绑定

image.png

isa分为两种,一种是直接绑定了类,另一种是nonponterIsa,因为isa指针占用8位也就是64bit,避免浪费,所有isa里存了类的地址外,还存有其他,这个需要进一步研究 这样我们就在开辟了一个内存来存储我们的对象了。

init, new 做了什么

init在底层又做了什么呢

image.png 通过源码可以看出,init什么也没做,直接返回了self,所以inti只是一种设计模式,为了方便开发者重写和在初始化的时候做一些事情

newalloc init 有什么区别呢

可以看出new其实就相当于alloc init,为了方便拓展传参还是尽量少使用new来初始化对象 image.png

总结

言不胜图,下面为alloc流程图

image.png

拓展

为什么 callAlloc 调用了两次

  • 调用alloc,先调用的是objc_alloc,而不是_objc_rootAlloc, 汇编那里也看到了,而且会调用两次callAlloc,这是因为调用alloc,在进行消息转发的时候,llvm会将所有的alloc,被修正到了objc_alloc 下面是llvm源码中的体现,可以看出当版本号大于8.0就会调用objc_alloc

image.png

为什么[NSOBject alloc] 时候 callAlloc只调用了一次

  • 因为在项目启动时 NSObjec类就已经调用了一次

  • 还有就是NSObjectobjc_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的概率很大
  • 这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降