001-alloc&init

前言

從一個對象的alloc開始,讓我們?nèi)隣C底層實現(xiàn),去探索學(xué)習(xí)OC源碼。

    LWPerson * obj = [LWPerson alloc];
    LWPerson * obj1 = [obj init];
    LWPerson * obj2 = [obj init];
    
    LWPerson * newObj = [LWPerson alloc];
 
    NSLog(@"%@---%p--%p",obj,obj,&obj);
    NSLog(@"%@---%p--%p",obj1,obj1,&obj1);
    NSLog(@"%@---%p--%p",obj2,obj2,&obj2);
    NSLog(@"%@---%p--%p",newObj,newObj,&newObj);
    <LWPerson: 0x281404710>---0x281404710--0x16b5cdbe0
    <LWPerson: 0x281404710>---0x281404710--0x16b5cdbd8
    <LWPerson: 0x281404740>---0x281404740--0x16b5cdbd0
截屏2021-07-05 下午10.02.42.png

初步總結(jié)

  • alloc具有開辟一塊內(nèi)存的功能,而init 沒有開辟內(nèi)存的功能
  • ps:棧區(qū) 開辟的內(nèi)存是高地址到低地址,堆區(qū)則是低地址到高地址(這里會延伸出一個奇怪的問題)

三種探索底層的方法

  1. 符號斷點
  2. 匯編 Xcode -> Debug -> Debug Workflow -> Always Show Disassembly
  3. 斷點 step into

源碼

alloc
+ (id)alloc {
    return _objc_rootAlloc(self);
}
_objc_rootAlloc
id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
callAlloc
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__ //判斷是不是 objc2.0版本
    //slowpath(x):x很可能為假,為真的概率很小 
    //fastpath(x):x很可能為真 
    //其實將fastpath和slowpath去掉是完全不影響任何功能,寫上是告訴編譯器對代碼進行優(yōu)化
    if (slowpath(checkNil && !cls)) return nil;
    //判斷該類是否實現(xiàn)自自定義的 +allocWithZone,沒有則進入if條件句
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
    }
#endif

    // No shortcuts available.
    if (allocWithZone) {
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
_objc_rootAllocWithZone
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}

zone參數(shù)被丟棄,開發(fā)者即使自定義allocWithZone方法也無法指定想使用的zone,對象所在zone實際上由libmalloc庫分配。

對象創(chuàng)建核心函數(shù)_class_createInstanceFromZone
截屏2021-07-05 下午10.19.56.png
  • cls->instanceSize: 對象所需內(nèi)存大小
  • (id)calloc(1,size): 開辟內(nèi)存,返回地址指針
  • obj->initInstanceIsa: 初始化isa指針,和類關(guān)聯(lián)起來
  • 返回obj(如果有cxxCtor,會調(diào)用初始化)

debug

        NSObject *n =  [NSObject alloc];
        LGPerson *p0 = [LGPerson alloc];
        LGPerson *p1  = [LGPerson alloc];
        NSLog(@"測試一下");

在creatInstance函數(shù)處打了斷點,分別打印三次alloc的堆棧

NSObject的alloc
截屏2021-07-05 下午10.44.34.png

流程:objc_alloc->callAloc->_objc_rootAllocWithZone->createInstance

LGPerson第一次alloc
截屏2021-07-05 下午10.47.45.png

流程:objc_alloc-> callAloc-> [NSObject alloc]-> _objc_rootAlloc->
callAlloc-> _objc_rootAllocWithZone->createInstance

LGPerson第二次alloc
截屏2021-07-05 下午10.48.41.png

流程:objc_alloc-> callAlloc-> _objc_rootAllocWithZone-> createInstance

幾個疑問

從函數(shù)棧看,[ SomeClass alloc]方法調(diào)用,都被轉(zhuǎn)化為objc_alloc函數(shù)調(diào)用,發(fā)生了什么,誰做的?
image.png

通過查看LLVM源碼,我們發(fā)現(xiàn)是編譯器把alloc消息發(fā)送,轉(zhuǎn)化了objc_init函數(shù)調(diào)用。至于為什么?
我的理解是這里可以看做是編譯器把alloc方法給hook了,在創(chuàng)建自定義類模板的第一個實例對象的時候,objc_alloc會再次發(fā)送alloc消息,調(diào)用alloc方法。其他時候objc_alloc函數(shù)調(diào)用會直接走到createInstane函數(shù)。對比第一次alloc,這顯然減少后續(xù)對象開辟內(nèi)存空間時的函數(shù)調(diào)用。

// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
為什么自定義類的第一次alloc和第二次alloc的函數(shù)調(diào)用堆棧不同呢?
  • debug調(diào)試看到hasCustomAWZ()返回值不同。我們第一次alloc多走了一段發(fā)送alloc消息的流程,在這個過程中我們的自定義類被實現(xiàn),被初始化,setInitialized()函數(shù)中,cache的_flag成員關(guān)于是有CustomAWZ的標(biāo)記位被重新賦值。
  • 除第一次外,后面alloc流程,自定義類如果沒有CustomAWZ,就會直接走_objc_rootAllocWithZone,不會再經(jīng)過消息發(fā)送,如果有就會發(fā)送allocWithZone:消息。
  • 這也側(cè)面說明,為什么llvm會把alloc消息發(fā)送轉(zhuǎn)換成objc_alloc,它既保證了我們類在第一次接收消息時被初始化,也避免了頻繁的alloc消息發(fā)送,直接進去了對象空間開辟流程。
  • 看到這里是不是很期待我們消息發(fā)送流程,以及它是如何觸發(fā)自定義類的初始化。
為什么NSObject的第一次alloc跟自定義類的不同,沒有走alloc方法調(diào)用呢?

這里不多說了,上圖


截屏2021-07-06 下午4.02.52.png
  • 很顯然NSObject作為基類,在main函數(shù)之前,程序加載過程中,libobjc就被觸發(fā)了NSObject的initialize方法。這也是我們在main函數(shù)執(zhí)行[NSObject alloc]直接進入_objc_rootAllocWithZone的原因。
  • 這里需要注意的一點是,NSObjcet類是有自定義awz的,看下面注釋,特殊處理了呀。
  // Special cases:
    // - NSObject AWZ  class methods are default.
  • 看到這是不是對我們iOS程序的加載流程也很好奇了。

畫個alloc流程圖吧
截屏2021-07-06 下午6.08.40.png

總結(jié):

  • LLVM在編譯時把所有的alloc消息發(fā)送,轉(zhuǎn)化為objc_alloc()
  • 自定義類的alloc流程,如果類沒有initialize,或者實現(xiàn)了customAWZ(都是customAWZ返回為true),會觸發(fā)alloc消息發(fā)送,否則將會直接進_objc_rootAlloc
  • 建議不要customAWZ,因為zone參數(shù)在底層實現(xiàn)是被丟棄的,實際分配zone不會被開發(fā)者決定(libMaolloc分配),反而讓alloc調(diào)用流程變復(fù)雜。
  • 下篇拓展下Align16,對齊

init

new

最后編輯于
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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