iOS底層原理 - alloc的流程圖

寫在前面

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

? 源碼

? Cooci司機objc4-756.2調(diào)試方案(Xcode11暫時無法斷點進(jìn)源碼)

一.準(zhǔn)備工作

? 下載好源碼,經(jīng)過一輪輪運行Carsh調(diào)試之后,可以通過 common+control+單擊 alloc 看到底層源碼的調(diào)用;

? 對于查看調(diào)用alloc具體源碼,我們可以使用斷點來分析:

? · 符號斷點

? · 調(diào)試欄:step into


調(diào)試欄

? ·顯示匯編代碼:菜單欄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];
}

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

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

1.alloc,objc_alloc 區(qū)分

從函數(shù)棧調(diào)用分析,走的是alloc方法。

xcode10 -> alloc,xcode11 -> objc_alloc ;(使用MachOView查看兩種編譯下的Mach-O文件,在_Data段__la_symbol_ptr 節(jié)中,我們可以看出在Xcode11下alloc的符號會被設(shè)置為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,希望編譯器進(jìn)行優(yōu)化;

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

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

3> fastpath(cls->canAllocFast())
里面調(diào)用了bit.canAllocFast 默認(rèn)返回false

4> id obj = class_createInstance(cls, 0)

內(nèi)部調(diào)用 _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. 判斷當(dāng)前class或者superclass是否有.cxx_construct 構(gòu)造方法的實現(xiàn)

2>hasCxxDtor()

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

3>canAllocNonpointer()

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

4>cls->instanceSize(extraBytes)

instanceSize 獲取類的大?。▊魅腩~外字節(jié)的大?。﹤魅胫禐閦one= false,fast = true,則(!zone && fast) = true

5>calloc()

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

6>initInstanceIsa()

內(nèi)部調(diào)用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調(diào)用順序

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 二進(jìn)制 :0000 0111 = 7 (4+2+1)
~WORD_MASK : 1111 1000
16二進(jìn)制為 : 0001 0000

1111 1000
0001 0000

0001 0000 = 16
?
所以 x = 16 也就是 8的倍數(shù)對齊,即 8 字節(jié)對齊

總結(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指針關(guān)聯(lián)對象——房子寫下名字

五.init & new

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

// 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 相當(dāng)于調(diào)用了alloc init

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

衍生:if(self = [super init]) 在 返回是instanceType 初始化時,常用到這種寫法,為什么這么寫呢?- 子類繼承于父類屬性,再判斷是否為空,為空則返回nil。確保是子類調(diào)用的方法和父類對應(yīng)

六 寫在后面

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

實踐出真知!

感謝以下大神的文章:

文章參考:iOS alloc流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容