iOS底层原理 - alloc的流程图

写在前面

​ iOS中内存空间创建,对象的创建会使用到alloc;今天我们来探索一下alloc的底层步骤。

​ 源码

​ Cooci司机objc4-756.2调试方案(Xcode11暂时无法断点进源码)

一.准备工作

​ 下载好源码,经过一轮轮运行Carsh调试之后,可以通过 common+control+单击 alloc 看到底层源码的调用;

​ 对于查看调用alloc具体源码,我们可以使用断点来分析:

​ · 符号断点

​ · 调试栏:step into


调试栏

​ ·显示汇编代码:菜单栏Debug->Debug Workflow->Always Show Disassembly

上面三种可以断到 objc_alloc 方法中

二.实际操作
//
//  main.m
//  objc-debug
//
//  Created by mark on 2020/03/9.
//
​
#import 
​
int main(int argc, const char * argv[]) {
 @autoreleasepool {
 // insert code here...
 NSObject *object = [NSObject alloc];
 NSLog(@"====== %@",object);
 }
 return 0;
}

​ 不出意外大家都可以来到这边

_objc_rootAlloc(Class cls)
{
 return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

​ 然后下面到了源码部分,看到是不是开始抓头了,按住续命穴我们继续

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
 if (slowpath(checkNil && !cls)) return nil;
​
#if __OBJC2__
 if (fastpath(!cls->ISA()->hasCustomAWZ())) {
 // No alloc/allocWithZone implementation. Go straight to the allocator.
 // fixme store hasCustomAWZ in the non-meta class and 
 // add it to canAllocFast's summary
 if (fastpath(cls->canAllocFast())) {
 // No ctors, raw isa, etc. Go straight to the metal.
 bool dtor = cls->hasCxxDtor();
 id obj = (id)calloc(1, cls->bits.fastInstanceSize());
 if (slowpath(!obj)) return callBadAllocHandler(cls);
 obj->initInstanceIsa(cls, dtor);
 return obj;
 }
 else {
 // Has ctor or raw isa or something. Use the slower path.
 id obj = class_createInstance(cls, 0);
 if (slowpath(!obj)) return callBadAllocHandler(cls);
 return obj;
 }
 }
#endif
​
 // No shortcuts available.
 if (allocWithZone) return [cls allocWithZone:nil];
 return [cls alloc];
}

源码比较多的修饰符和转义字符,看起来是会比较枯燥,不然头发为啥越来越少了呢(头发旺盛的略过),下面我们来分析一下

三.alloc 流程图
alloc 流程图.png

1.alloc,objc_alloc 区分

从函数栈调用分析,走的是alloc方法。

xcode10 -> alloc,xcode11 -> objc_alloc ;(使用MachOView查看两种编译下的Mach-O文件,在_Data段__la_symbol_ptr 节中,我们可以看出在Xcode11下alloc的符号会被设置为objc_alloc,而xcode10却没有)

2.callAlloc方法

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
 if (slowpath(checkNil && !cls)) return nil;
​
#if __OBJC2__
 if (fastpath(!cls->ISA()->hasCustomAWZ())) {
 // No alloc/allocWithZone implementation. Go straight to the allocator.
 // fixme store hasCustomAWZ in the non-meta class and 
 // add it to canAllocFast's summary
 if (fastpath(cls->canAllocFast())) {
 // No ctors, raw isa, etc. Go straight to the metal.
 bool dtor = cls->hasCxxDtor();
 id obj = (id)calloc(1, cls->bits.fastInstanceSize());
 if (slowpath(!obj)) return callBadAllocHandler(cls);
 obj->initInstanceIsa(cls, dtor);
 return obj;
 }
 else {
 // Has ctor or raw isa or something. Use the slower path.
 id obj = class_createInstance(cls, 0);
 if (slowpath(!obj)) return callBadAllocHandler(cls);
 return obj;
 }
 }
#endif
​
 // No shortcuts available.
 if (allocWithZone) return [cls allocWithZone:nil];
 return [cls alloc];
}

1> slowpath(checkNil && !cls)

两个优化比较:slowpath(x),fastpath(x); slowpath(x) :x为0,希望编译器优化;x大概率是有值的,不用每次都读取。fastpath(x):表示x很可能不为0,希望编译器进行优化;

2> fastpath(!cls->ISA()->hasCustomAWZ())

hasCustomAWZ()方法表示 hasCustomAllocWithZone,这里表示没有alloc/allocWithZone的实现

3> fastpath(cls->canAllocFast())
里面调用了bit.canAllocFast 默认返回false

4> id obj = class_createInstance(cls, 0)

内部调用 _class_createInstanceFromZone(cls, extraBytes, nil)

3._class_createInstanceFromZone方法

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
 bool cxxConstruct = true, 
 size_t *outAllocatedSize = nil)
{
 if (!cls) return nil;
​
 assert(cls->isRealized());
​
 // Read class's info bits all at once for performance
 bool hasCxxCtor = cls->hasCxxCtor();
 bool hasCxxDtor = cls->hasCxxDtor();
 bool fast = cls->canAllocNonpointer();
​
 size_t size = cls->instanceSize(extraBytes);
 if (outAllocatedSize) *outAllocatedSize = size;
​
 id obj;
 if (!zone  &&  fast) {
 obj = (id)calloc(1, size);
 if (!obj) return nil;
 obj->initInstanceIsa(cls, hasCxxDtor);
 } 
 else {
 if (zone) {
 obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
 } else {
 obj = (id)calloc(1, size);
 }
 if (!obj) return nil;
​
 // Use raw pointer isa on the assumption that they might be 
 // doing something weird with the zone or RR.
 obj->initIsa(cls);
 }
​
 if (cxxConstruct && hasCxxCtor) {
 obj = _objc_constructOrFree(obj, cls);
 }
​
 return obj;
}

1>hasCxxCtor()

addSubclass() propagates this flag from the superclass. 判断当前class或者superclass是否有.cxx_construct 构造方法的实现

2>hasCxxDtor()

hasCxxDtor()是判断判断当前class或者superclass是否有.cxx_destruct 析构方法的实现

3>canAllocNonpointer()

anAllocNonpointer()是具体标记某个类是否支持优化的isa

4>cls->instanceSize(extraBytes)

instanceSize 获取类的大小(传入额外字节的大小)传入值为zone= false,fast = true,则(!zone && fast) = true

5>calloc()

用于动态开辟内存,没有具体实践代码。在接下来的文章里面会讲到malloc源码

6>initInstanceIsa()

内部调用initIsa(cls, true, hasCxxDtor)初始化isa

这一步已经完成了初始化isa并开辟内存空间,那我们来看看instanceSize做了什么

4.字节对齐 - ( instanceSize探索)

#ifdef __LP64__
#   define WORD_SHIFT 3UL
#   define WORD_MASK 7UL
#   define WORD_BITS 64
#else
#   define WORD_SHIFT 2UL
#   define WORD_MASK 3UL
#   define WORD_BITS 32
#endif
​
static inline uint32_t word_align(uint32_t x) {
 return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
 return (x + WORD_MASK) & ~WORD_MASK;
}
​
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
 return word_align(unalignedInstanceSize());
}
​
size_t instanceSize(size_t extraBytes) {
 size_t size = alignedInstanceSize() + extraBytes;
 // CF requires all objects be at least 16 bytes.
 if (size < 16) size = 16;
 return size;
}

我们来过下instanceSize调用顺序

instanceSize(extraBytes) -> alignedInstanceSize ->word_align(unalignedInstanceSize())

1>instanceSize(extraBytes)

这个方法是获取类大小

2>alignedInstanceSize()

获取类所需要的内存空间大小

3>unalignedInstanceSize()

data()->ro->instanceSize就是获取这个类所有属性内存的大小。这里只有继承NSObject的一个属性isa——返回8字节

4>word_align

字节对齐,在64位系统下,对象大小采用8字节对齐法

5>if (size < 16) size = 16

CoreFoundation需要所有对象之和至少是16字节

5.字节对齐算法-(实现)

假如: x = 9,已知WORD_MASK = 7
​
x + WORD_MASK = 9 + 7 = 16
WORD_MASK 二进制 :0000 0111 = 7 (4+2+1)
~WORD_MASK : 1111 1000
16二进制为 : 0001 0000

1111 1000
0001 0000

0001 0000 = 16
​
所以 x = 16 也就是 8的倍数对齐,即 8 字节对齐

总结:对象大小为16字节,必定是8的倍数

疑问:为什么要使用8字节对齐算法呢?

简单画了个示意图,上边是紧紧挨着,下面是8字节为一格。如果cpu存数据的时候紧紧挨着,读取的时候要不断变化读取长度,所以这时候就采用了空间换时间的做法

那为什么是8字节?不是4字节或是16字节?

——因为内存中8字节的指针比较多

四.alloc 实际流程图
alloc 实际流程图.png

instanceSize计算内存大小——量房子

calloc申请开辟内存——造房子

initInstanceIsa指针关联对象——房子写下名字

五.init & new

init什么也不做,就是给开发者使用工厂设计模式提供一个接口

// Replaced by CF (throws an NSException)
+ (id)init {
 return (id)self;
}
​
- (id)init {
 return _objc_rootInit(self);
}
​
id
_objc_rootInit(id obj)
{
 // In practice, it will be hard to rely on this function.
 // Many classes do not properly chain -init calls.
 return obj;
}

new 相当于调用了alloc init

+ (id)new {
 return [callAlloc(self, false/*checkNil*/) init];
}

衍生:if(self = [super init]) 在 返回是instanceType 初始化时,常用到这种写法,为什么这么写呢?- 子类继承于父类属性,再判断是否为空,为空则返回nil。确保是子类调用的方法和父类对应

六 写在后面

工欲善其事必先利其器。只有在理解底层源码的同事,才有创新。从枯燥的源码慢慢啃下来,通过大神文章和gitHub大神注释理解,拆开一步步研究

实践出真知!

感谢以下大神的文章:

文章参考:iOS alloc流程

你可能感兴趣的:(iOS底层原理 - alloc的流程图)