OC底層原理三:探索alloc (你好摔吏,alloc大佬 )

OC底層原理 學(xué)習(xí)大綱

OC對(duì)象的始源 - alloc

前言

我們都知道边酒,創(chuàng)建OC對(duì)象的2種方式: [[ClassName alloc]init][ClassName new]

當(dāng)被問起他們的作用時(shí)赂摆,可能你的回答是: alloc + init給對(duì)象開辟內(nèi)存空間并完成對(duì)象初始化,new是類方法挟憔,實(shí)現(xiàn)的功能一樣。

這個(gè)描述沒有錯(cuò)烟号,但請(qǐng)?jiān)敿?xì)描述下他們的作用绊谭。

可能你一臉懵逼。 心里已經(jīng)開罵: 你丫有病吧汪拥,我已經(jīng)說完了呀4锎!

今天迫筑,我將幫你扯開alloc宪赶、init、new的這塊遮羞布铣焊。 讓你深入了解alloc、init罕伯、new曲伊。

前期準(zhǔn)備

課前問題

打開objc4-781包,在HTTest文件夾中,創(chuàng)建HTPerson測(cè)試文件(繼承自NSObject),切換項(xiàng)目target為HTTest,在main.m文件中加入測(cè)試代碼追他。

image.png

%@ 打印對(duì)象 %p 打印地址 &p 指針地址

問題:

  1. p1坟募、p2p3對(duì)象和地址打印都一致邑狸, 為何&p打印不一致
  2. p4的地址為什么和p1懈糯、p2p3都不一樣单雾。

學(xué)完本章赚哗,你就徹底懂了

本節(jié)內(nèi)容:

  1. alloc流程
  2. alloc核心函數(shù)
  3. alloc的地位(init她紫、new)

1. alloc流程

打開源碼工程,跟隨alloc函數(shù)屿储,一步步深入贿讹。流程如下:

alloc流程.png

當(dāng)出現(xiàn)分支時(shí),我們可以添加斷點(diǎn)够掠,輔助查看主流程是進(jìn)入哪個(gè)分支民褂。

不知道打斷點(diǎn),可參考OC底層原理一:定位源碼(歡迎來到底層世界)內(nèi)的三種斷點(diǎn)技巧疯潭。

callAlloc處出現(xiàn)了分支赊堪。斷點(diǎn)后發(fā)現(xiàn)程序走向_objc_rootAllocWithZone分支,繼而進(jìn)入_class_createInstanceFromZone函數(shù)竖哩。

image.png

關(guān)于fastpathslowpath的作用哭廉,請(qǐng)移步OC底層原理四: 編譯器優(yōu)化

allocwithZone: 和alloc一樣,為對(duì)象分配足夠的內(nèi)存期丰, cocoa 會(huì)遍歷該對(duì)象所有的成員變量群叶,通過成員變量的類型來計(jì)算所需占用的內(nèi)存。從iOS8以后钝荡,Zone外層API已被廢棄街立,僅底層源碼做兼容處理。

_class_createInstanceFromZone是最底層的包工頭埠通。?? 終于找到真正干活的人了赎离。它實(shí)現(xiàn)三大核心方法,然后將成品obj返回給外層端辱。

_class_createInstanceFromZone核心方法.png

2.alloc核心函數(shù)

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;

    // 1. 計(jì)算開辟的內(nèi)存大小
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
      // 2. 申請(qǐng)內(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) {
        // 初始化isa并與objc關(guān)聯(lián)
        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)) {
        // 返回成品對(duì)象
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}
  • hasCxxCtor梁剔、hasCxxDtor、fast等 后續(xù)剖析isa會(huì)詳細(xì)講解
1. 計(jì)算內(nèi)存大小: instanceSize
    size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }

        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

if (size < 16) size = 16 :做了小于16字節(jié)的判斷舞蔽。
跟斷點(diǎn)荣病,發(fā)現(xiàn)主流程進(jìn)入cache.fastInstanceSize(extraBytes)

 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);
        }
    }

繼續(xù)跟斷點(diǎn),進(jìn)入align16

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

align16的實(shí)現(xiàn)渗柿,就是使用位運(yùn)算算法完成16字節(jié)對(duì)齊个盆。

算法(x + size_t(15)) & ~size_t(15)
x=8為例,計(jì)算過程如下:

  • 8 + size(15) = 23 二進(jìn)制-> 0000 0000 0001 0111
  • size_t(15) 二進(jìn)制-> 0000 0000 0000 1111
  • 取反~size_t(15) 二進(jìn)制-> 1111 1111 1111 0000
  • 求交 & :
    0000 0000 0001 0111 & 1111 1111 1111 0000 = 0000 0000 0001 0000
  • 結(jié)果表示為十進(jìn)制: 16

目的:

  • 提高性能,加快存儲(chǔ)速度
    通常內(nèi)存是由一個(gè)個(gè)字節(jié)組成朵栖,cpu在存儲(chǔ)數(shù)據(jù)時(shí)颊亮,是以固定字節(jié)塊為單位進(jìn)行存取的。這是一個(gè)空間換時(shí)間的優(yōu)化方式陨溅,這樣不用考慮字節(jié)未對(duì)齊的數(shù)據(jù)终惑,極大節(jié)省了計(jì)算資源,提升了存取速度门扇。
  • 更安全
    在一個(gè)對(duì)象中,isa占8字節(jié)雹有,對(duì)象屬性也占8字節(jié)偿渡。蘋果公司現(xiàn)在采用16字節(jié)對(duì)齊,當(dāng)對(duì)象無屬性是件舵,會(huì)預(yù)留8字節(jié)卸察,即16字節(jié)對(duì)齊。 如果不預(yù)留铅祸,CPU存取時(shí)以16字節(jié)為單位長(zhǎng)度去訪問坑质,會(huì)訪問到相鄰對(duì)象,造成訪問混亂临梗。

執(zhí)行完后涡扼,回到上層函數(shù)size = cls->instanceSize(extraBytes)可打印size值。
此時(shí)已完成內(nèi)存大小計(jì)算盟庞。

image.png
2. 分配內(nèi)存 calloc

根據(jù)size 大小進(jìn)行內(nèi)存分配

image.png
  • 執(zhí)行前打印obj只有cls類名,執(zhí)行后打印吃沪,已成功申請(qǐng)內(nèi)存首地址

  • 但并不是我們想象中的格式<HTPerson: 0x10069eff0>,這是因?yàn)檫@一步只是單純的完成內(nèi)存申請(qǐng)什猖,返回首地址票彪。

  • 類和地址的綁定是下一步initInstanceIsa的工作

3. initInstanceIsa

初始化isa,完成與類的綁定

obj->initInstanceIsa(cls, hasCxxDtor);
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

具體的isa結(jié)構(gòu)和綁定關(guān)系不狮,后續(xù)會(huì)作為單獨(dú)章節(jié)進(jìn)行講解

isainit之后加斷點(diǎn)降铸,打印obj,此時(shí)發(fā)現(xiàn)地址與類完成綁定

image.png

總結(jié): 至此,我們已對(duì)alloc有了完整的認(rèn)知

3. alloc的地位(init摇零、new)

可能你有疑問推掸,alloc把活都干完了,init和new干啥驻仅?

init

進(jìn)入init

+ (id)init {
    return (id)self;
}

- (id)init {
    return _objc_rootInit(self);
}

進(jìn)入_objc_rootInit

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;
}
  • 我們發(fā)現(xiàn)init的類方法和對(duì)象方法返回的都是id對(duì)象本身谅畅。
  • 不同的是類方法返回了一個(gè)id類型的self,這是為了可以給開發(fā)者提供自定義構(gòu)造方法的入口噪服,通過id強(qiáng)轉(zhuǎn)類型實(shí)現(xiàn)工廠設(shè)計(jì)毡泻,返回我們定義的類型。

new

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

實(shí)際上就是完成調(diào)用了callAlloc粘优,走的時(shí)alloc流程仇味。

唯一區(qū)別:

  • alloc + init允許對(duì)init進(jìn)行重寫,可自定制init完成工廠設(shè)計(jì)
  • new是完整封裝敬飒,無法在初始化這一步加入自定制需求

答案

image.png
  • 問題1: p1邪铲、p2芬位、p3對(duì)象和地址打印都一致无拗, 為何&p打印不一致

alloc是真正開辟內(nèi)存和綁定對(duì)象的,p1昧碉、p2英染、p3共用1個(gè)alloc揽惹,所以他們都是指向同一目的地址。但是他們本身也是對(duì)象四康,在init時(shí)傳入他們自身id搪搏,&p打印的是他們自身的地址。

通俗的說:
我有一個(gè)房子出售闪金,A疯溺、B、C三個(gè)都是我員工哎垦,他們都領(lǐng)著客戶來看我這套房子囱嫩。但是他們?nèi)齻€(gè)雖然都是我公司員工,但工號(hào)(id)不一樣漏设。
如果客戶問他們房子在哪(等同于%@%p打印)墨闲,他們都會(huì)告訴我房子的具體位置(三人說的一定相同)。
如果顧客問他們是誰(shuí)(等同于打印&p),他們就會(huì)各自回答A郑口、B鸳碧、C。

  • 問題2. p4的地址為什么和p1犬性、p2瞻离、p3都不一樣。

因?yàn)閜1仔夺、p2琐脏、p3是同一個(gè)alloc打印的,而p4是new出來的缸兔,new會(huì)單獨(dú)調(diào)用alloc日裙。 所以他們打印肯定不一樣

下一節(jié): OC底層原理五: NSObject的alloc分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惰蜜,隨后出現(xiàn)的幾起案子昂拂,更是在濱河造成了極大的恐慌,老刑警劉巖抛猖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件格侯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡财著,警方通過查閱死者的電腦和手機(jī)联四,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撑教,“玉大人朝墩,你說我怎么就攤上這事∥敖悖” “怎么了收苏?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵亿卤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我鹿霸,道長(zhǎng)排吴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任懦鼠,我火速辦了婚禮钻哩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肛冶。我一直安慰自己憋槐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布淑趾。 她就那樣靜靜地躺著阳仔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扣泊。 梳的紋絲不亂的頭發(fā)上近范,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音延蟹,去河邊找鬼评矩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛阱飘,可吹牛的內(nèi)容都是我干的斥杜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼沥匈,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蔗喂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起高帖,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤缰儿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后散址,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乖阵,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年预麸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瞪浸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吏祸,死狀恐怖对蒲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤齐蔽,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站床估,受9級(jí)特大地震影響含滴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丐巫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一谈况、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧递胧,春花似錦碑韵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至遗菠,卻和暖如春联喘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辙纬。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工豁遭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贺拣。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓蓖谢,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親譬涡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闪幽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355