iOS底層原理 - alloc的流程圖

寫在前面

? iOS中內(nèi)存空間創(chuàng)建哼丈,對象的創(chuàng)建會使用到alloc;今天我們來探索一下alloc的底層步驟。

? 源碼

? Cooci司機objc4-756.2調試方案(Xcode11暫時無法斷點進源碼)

一.準備工作

? 下載好源碼,經(jīng)過一輪輪運行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*/);
}

? 然后下面到了源碼部分伟葫,看到是不是開始抓頭了,按住續(xù)命穴我們繼續(xù)

// 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];
}

源碼比較多的修飾符和轉義字符院促,看起來是會比較枯燥筏养,不然頭發(fā)為啥越來越少了呢(頭發(fā)旺盛的略過),下面我們來分析一下

三.alloc 流程圖
alloc 流程圖.png

1.alloc,objc_alloc 區(qū)分

從函數(shù)棧調用分析常拓,走的是alloc方法渐溶。

xcode10 -> alloc,xcode11 -> objc_alloc 弄抬;(使用MachOView查看兩種編譯下的Mach-O文件茎辐,在_Data段__la_symbol_ptr 節(jié)中,我們可以看出在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)

兩個優(yōu)化比較:slowpath(x)眉睹,fastpath(x); slowpath(x) :x為0荔茬,希望編譯器優(yōu)化;x大概率是有值的竹海,不用每次都讀取。fastpath(x):表示x很可能不為0丐黄,希望編譯器進行優(yōu)化;

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

hasCustomAWZ()方法表示 hasCustomAllocWithZone斋配,這里表示沒有alloc/allocWithZone的實現(xiàn)

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

4> id obj = class_createInstance(cls, 0)

內(nèi)部調用 _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 構造方法的實現(xiàn)

2>hasCxxDtor()

hasCxxDtor()是判斷判斷當前class或者superclass是否有.cxx_destruct 析構方法的實現(xiàn)

3>canAllocNonpointer()

anAllocNonpointer()是具體標記某個類是否支持優(yōu)化的isa

4>cls->instanceSize(extraBytes)

instanceSize 獲取類的大小(傳入額外字節(jié)的大泄喙搿)傳入值為zone= false,fast = true艰争,則(!zone && fast) = true

5>calloc()

用于動態(tài)開辟內(nèi)存,沒有具體實踐代碼桂对。在接下來的文章里面會講到malloc源碼

6>initInstanceIsa()

內(nèi)部調用initIsa(cls, true, hasCxxDtor)初始化isa

這一步已經(jīng)完成了初始化isa并開辟內(nèi)存空間甩卓,那我們來看看instanceSize做了什么

4.字節(jié)對齊 - ( 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()

獲取類所需要的內(nèi)存空間大小

3>unalignedInstanceSize()

data()->ro->instanceSize就是獲取這個類所有屬性內(nèi)存的大小。這里只有繼承NSObject的一個屬性isa——返回8字節(jié)

4>word_align

字節(jié)對齊蕉斜,在64位系統(tǒng)下逾柿,對象大小采用8字節(jié)對齊法

5>if (size < 16) size = 16

CoreFoundation需要所有對象之和至少是16字節(jié)

5.字節(jié)對齊算法-(實現(xiàn))

假如: 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的倍數(shù)對齊,即 8 字節(jié)對齊

總結:對象大小為16字節(jié)机错,必定是8的倍數(shù)

疑問:為什么要使用8字節(jié)對齊算法呢爬范?

簡單畫了個示意圖,上邊是緊緊挨著弱匪,下面是8字節(jié)為一格青瀑。如果cpu存數(shù)據(jù)的時候緊緊挨著,讀取的時候要不斷變化讀取長度萧诫,所以這時候就采用了空間換時間的做法

那為什么是8字節(jié)斥难?不是4字節(jié)或是16字節(jié)?

——因為內(nèi)存中8字節(jié)的指針比較多

四.alloc 實際流程圖
alloc 實際流程圖.png

instanceSize計算內(nèi)存大小——量房子

calloc申請開辟內(nèi)存——造房子

initInstanceIsa指針關聯(lián)對象——房子寫下名字

五.init & new

init什么也不做帘饶,就是給開發(fā)者使用工廠設計模式提供一個接口

// 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提茁。確保是子類調用的方法和父類對應

六 寫在后面

工欲善其事必先利其器淹禾。只有在理解底層源碼的同事,才有創(chuàng)新茴扁。從枯燥的源碼慢慢啃下來铃岔,通過大神文章和gitHub大神注釋理解,拆開一步步研究

實踐出真知峭火!

感謝以下大神的文章:

文章參考:iOS alloc流程

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
禁止轉載毁习,如需轉載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末卖丸,一起剝皮案震驚了整個濱河市纺且,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌稍浆,老刑警劉巖载碌,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異衅枫,居然都是意外死亡嫁艇,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門弦撩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來步咪,“玉大人,你說我怎么就攤上這事益楼』” “怎么了点晴?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長静袖。 經(jīng)常有香客問我觉鼻,道長,這世上最難降的妖魔是什么队橙? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任坠陈,我火速辦了婚禮,結果婚禮上捐康,老公的妹妹穿的比我還像新娘仇矾。我一直安慰自己,他們只是感情好解总,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布贮匕。 她就那樣靜靜地躺著,像睡著了一般花枫。 火紅的嫁衣襯著肌膚如雪刻盐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天劳翰,我揣著相機與錄音敦锌,去河邊找鬼。 笑死佳簸,一個胖子當著我的面吹牛乙墙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播生均,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼听想,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了马胧?” 一聲冷哼從身側響起汉买,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎漓雅,沒想到半個月后录别,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡邻吞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了葫男。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抱冷。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖梢褐,靈堂內(nèi)的尸體忽然破棺而出旺遮,到底是詐尸還是另有隱情赵讯,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布耿眉,位于F島的核電站边翼,受9級特大地震影響,放射性物質發(fā)生泄漏鸣剪。R本人自食惡果不足惜组底,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望筐骇。 院中可真熱鬧债鸡,春花似錦、人聲如沸铛纬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽告唆。三九已至棺弊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間擒悬,已是汗流浹背模她。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留茄螃,地道東北人缝驳。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像归苍,于是被迫代替她去往敵國和親用狱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345