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 <Foundation/Foundation.h>
​
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流程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350