alloc流程分析

OC作為一門面向?qū)ο蟮恼Z言扫步,那么對于對象的創(chuàng)建方法的探索流程就必不可少庐舟。下面我們就探索一下關(guān)于對象在創(chuàng)建時開辟內(nèi)存的alloc方法的流程猪半。

一、源碼

探索之前我們需要一份最新的objc4-781官方源碼進行調(diào)試在旱,可參考cooci最新的objc4-779.1源碼編譯調(diào)試方法進行調(diào)試
objc官方源碼
老司機最新macOS 10.15下objc4-779.1源碼編譯調(diào)試

二摇零、源碼調(diào)試方式

有了源碼之后我們該如何調(diào)試alloc方法呢?

1. 符號斷點

在下alloc符號斷點的時候需要注意桶蝎,不用運行之前就打開符號斷點驻仅,因為所有的對象都有alloc方法,為了確保是自己創(chuàng)建的對象的alloc方法登渣,我們需要先在自己對象的alloc方法前加一個斷點噪服,然后執(zhí)行到斷點處,我們再打開alloc符號斷點胜茧,就進入了我們自己對象的alloc方法內(nèi)部了粘优。


符號斷點.png

斷點符號名稱.png
2. Step into

和方法1一樣,先在自己對象的alloc方法前加個斷點呻顽,執(zhí)行到斷點處雹顺,按住control鍵,Step into進入方法

Step into.png

objc_alloc.png

所在庫的位置.png

3. 匯編方式

和方法1一樣芬位,先在自己對象的alloc方法前加個斷點无拗,執(zhí)行到斷點處,Debug -> Debug Workflow -> Always Show Disassembly打開匯編模式昧碉,Step into斷點進入?yún)R編頁面

匯編方式.png

和方法2相同英染,找到objc_alloc方法,step into進入方法內(nèi)部查看所在庫的位置
匯編頁面.png

三被饿、alloc源碼調(diào)試

有了源碼和知道了調(diào)試方式之后四康,便來到了第三步——alloc源碼調(diào)試

1. alloc方法
+ (id)alloc {
    return _objc_rootAlloc(self);
}
2. _objc_rootAlloc方法
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

fastpath表示條件更可能成立
slowpath表示條件更不可能成立

其實將fastpathslowpath去掉是完全不影響任何功能的。之所以將fastpathslowpath放到if語句中狭握,是為了告訴編譯器闪金,if中的條件是大概率fastpath還是小概率slowpath事件,從而讓編譯器對代碼進行優(yōu)化论颅。知道了這些哎垦,我們就可以來繼續(xù)看源碼了:

3. callAlloc方法
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    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));
}
4. _objc_rootAllocWithZone方法
_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);
}
5. _class_createInstanceFromZone方法
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;

    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        // alloc 開辟內(nèi)存的地方
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}
6. cls->instanceSize & calloc & obj->initInstanceIsa

① cls->instanceSize:先計算出需要的內(nèi)存空間大小

  1. 首先判斷是否有緩存,有的話采用內(nèi)存對齊方法計算所需內(nèi)存大小
  2. 如果沒有緩存恃疯,則計算內(nèi)存大小漏设,如果size 小于 16,最小取16
size_t instanceSize(size_t extraBytes) const {
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) { // // 判斷是否有緩存
        return cache.fastInstanceSize(extraBytes); // 內(nèi)存對齊
    }
     // 計算類中所有屬性的大小 + 額外的字節(jié)數(shù)0
    size_t size = alignedInstanceSize() + extraBytes; 
    // 如果size 小于 16今妄,最小取16
    if (size < 16) size = 16;
    return size;
}

fastInstanceSize方法:快速計算內(nèi)存大小方法

size_t fastInstanceSize(size_t extra) const
{
    ASSERT(hasFastInstanceSize(extra));

    if (__builtin_constant_p(extra) && extra == 0) {
        return _flags & FAST_CACHE_ALLOC_MASK16;
    } else {
        size_t size = _flags & FAST_CACHE_ALLOC_MASK;
        // remove the FAST_CACHE_ALLOC_DELTA16 that was added
        // by setFastInstanceSize
        return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
    }
}

align16方法:內(nèi)存對齊方法

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

內(nèi)存對齊算法:

假設(shè)傳入的參數(shù): x = 8
x + size_t(15) = 8 + 15 = 23
x + size_t(15) 二進制:
0000 0000 0001 0111 = 23
size_t(15) 二進制 :
0000 0000 0000 1111 = 15
~size_t(15) :
1111 1111 1111 0000
x + size_t(15) & ~size_t(15):0000 0000 0001 0000 = 16

所以返回值 (x + size_t(15)) & ~size_t(15) = 16(原始值:8) 也就是 16 的倍數(shù)對齊郑口,即 16 字節(jié)對齊

② calloc:向系統(tǒng)申請開辟內(nèi)存,返回地址指針
通過instanceSize計算的內(nèi)存大小,向內(nèi)存中申請大小為 size的內(nèi)存盾鳞,并賦值給obj犬性,因此obj是指向內(nèi)存地址的指針

// alloc 開辟內(nèi)存的地方
obj = (id)calloc(1, size);

這里我們可以通過斷點來印證上述的說法,在未執(zhí)行calloc時腾仅,po objnil乒裆,執(zhí)行后,再po obj發(fā)現(xiàn)推励,返回了一個16進制的地址

未執(zhí)行calloc和執(zhí)行calloc后

在平常的開發(fā)中鹤耍,一般一個對象的打印的格式都是類似于這樣的<LGPerson: 0x01111111f>(是一個指針)。為什么這里不是呢吹艇?

  • 主要是因為objc地址 還沒有與傳入 的cls進行關(guān)聯(lián)
  • 同時印證了alloc的根本作用就是開辟內(nèi)存

③ obj->initInstanceIsa:關(guān)聯(lián)到相應(yīng)的類

執(zhí)行initInstanceIsa后
7. 總結(jié)

根據(jù)源碼調(diào)試方法惰蜜,我們會得到alloc方法的執(zhí)行流程如下:


alloc流程.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市受神,隨后出現(xiàn)的幾起案子抛猖,更是在濱河造成了極大的恐慌,老刑警劉巖鼻听,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件财著,死亡現(xiàn)場離奇詭異,居然都是意外死亡撑碴,警方通過查閱死者的電腦和手機撑教,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來醉拓,“玉大人伟姐,你說我怎么就攤上這事收苏。” “怎么了愤兵?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵鹿霸,是天一觀的道長。 經(jīng)常有香客問我秆乳,道長懦鼠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任屹堰,我火速辦了婚禮肛冶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扯键。我一直安慰自己睦袖,他們只是感情好,可當我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布忧陪。 她就那樣靜靜地躺著扣泊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嘶摊。 梳的紋絲不亂的頭發(fā)上延蟹,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天,我揣著相機與錄音叶堆,去河邊找鬼阱飘。 笑死,一個胖子當著我的面吹牛虱颗,可吹牛的內(nèi)容都是我干的沥匈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼忘渔,長吁一口氣:“原來是場噩夢啊……” “哼高帖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起畦粮,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤散址,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后宣赔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體预麸,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年儒将,在試婚紗的時候發(fā)現(xiàn)自己被綠了吏祸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡钩蚊,死狀恐怖贡翘,靈堂內(nèi)的尸體忽然破棺而出蹈矮,到底是詐尸還是另有隱情,我是刑警寧澤床估,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布含滴,位于F島的核電站诱渤,受9級特大地震影響丐巫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜勺美,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一递胧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧赡茸,春花似錦缎脾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至华蜒,卻和暖如春辙纬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叭喜。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工贺拣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捂蕴。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓譬涡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親啥辨。 傳聞我的和親對象是個殘疾皇子涡匀,可洞房花燭夜當晚...
    茶點故事閱讀 44,947評論 2 355