iOS 之1--alloc流程初探

背景:在iOS中萬物皆對(duì)像粉寞,平時(shí)我們?cè)陂_發(fā)中經(jīng)常用到alloc這個(gè)方法妙同,但是底層是如何實(shí)現(xiàn)的呢杈湾!帶著這個(gè)疑問我們一起從對(duì)象NSObject的初始化alloc開始探索。

一费变、前話:調(diào)試方法

在開始調(diào)試之前摧扇,我們先了解一下幾種調(diào)試的方法
// 介紹三種方式// libobjc.A.dylib

  1. 下斷點(diǎn) : control + in - objc_alloc
  2. 下符號(hào)斷點(diǎn) objc_alloc: libobjc.A.dylib+[NSObject alloc]
  3. 匯編,通過Debug->Debug WorkFlow->Allway Show

Disassembly 打開匯編界面挚歧,可看到調(diào)用 libobjc.A.dylib`objc_alloc:

二扛稽、alloc流程分析

  • 1.新建類MyTeacher繼承自NSObject,在objct源碼內(nèi)的main方法內(nèi)寫以下代碼,在[MyTeacher alloc]方法處打上斷點(diǎn)滑负,然后進(jìn)行調(diào)試在张。
#import <Foundation/Foundation.h>
#import "MyTeacher.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // size :
        // 1: 對(duì)象需要的內(nèi)存空間 8倍數(shù) - 8字節(jié)對(duì)齊
        // 2: 最少16 安全 16 - 8  > 16
        MyTeacher  *p = [MyTeacher alloc];//此處打斷點(diǎn)
        NSLog(@"%@",p);
    }
    return 0;
}
  • 2.第一步會(huì)進(jìn)入NSObject類中的alloc 方法
+ (id)alloc {
    return _objc_rootAlloc(self);
}
  • 3.我們?cè)龠M(jìn)一步查看 _objc_rootAlloc 方法
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
  • 4.我們?cè)龠M(jìn)一步查看 callAlloc 方法用含,通過進(jìn)一步分析cls->canAllocFast(),此方法一直返回false帮匾,所以我們直接研究else的代碼啄骇,進(jìn)一步研究class_createInstance
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
 /* 省略代碼*/
        // canAllocFast返回值固定為false,內(nèi)部調(diào)用了一個(gè)bits. canAllocFast, 所以創(chuàng)建對(duì)象暫時(shí)看來只能用到else中的代碼
        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 { //*****執(zhí)行以下方法辟狈,上面的先不看*****
            // 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;
        }
   /* 省略代碼*/
}
  • 5.接下來我們進(jìn)入 *** class_createInstance(cls, 0) *** 研究肠缔, 調(diào)用_class_createInstanceFromZone(cls, extraBytes, nil)
id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}
    1. 我們看一下_class_createInstanceFromZone(cls, extraBytes, nil)
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();

   //此處計(jì)算內(nèi)存的大小
    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {//********此時(shí)為true夏跷,會(huì)執(zhí)行以下代碼******
        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;
}

詳細(xì)解讀
①hasCxxCtor()
hasCxxCtor()是判斷當(dāng)前class或者superclass是否有.cxx_construct 構(gòu)造方法的實(shí)現(xiàn)
②hasCxxDtor()
hasCxxDtor()是判斷判斷當(dāng)前class或者superclass是否有.cxx_destruct 析構(gòu)方法的實(shí)現(xiàn)
③canAllocNonpointer()
anAllocNonpointer()是具體標(biāo)記某個(gè)類是否支持優(yōu)化的isa
④instanceSize()
instanceSize()獲取類的大泻咦(傳入額外字節(jié)的大小)
已知zone=false槽华,fast=true壹蔓,則(!zone && fast)=true
⑤calloc()
用來動(dòng)態(tài)開辟內(nèi)存,沒有具體實(shí)現(xiàn)代碼猫态,接下來的文章會(huì)講到malloc源碼
⑥initInstanceIsa()
內(nèi)部調(diào)用initIsa(cls, true, hasCxxDtor)初始化isa

    1. 其中 size_t size = cls->instanceSize(extraBytes)是計(jì)算內(nèi)存的大小佣蓉,此處我們進(jìn)行分析一下,了解一下如何內(nèi)存分配亲雪,其中64位系統(tǒng)下勇凭,對(duì)象大小采用8字節(jié)對(duì)齊,但是實(shí)際申請(qǐng)的內(nèi)存最低為16字節(jié)义辕。然后調(diào)用calloc(1, size);函數(shù)為對(duì)象分配內(nèi)存空間虾标。
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;
}

uint32_t alignedInstanceSize() {
  return word_align(unalignedInstanceSize());
}

// May be unaligned depending on class's ivars.
//讀取當(dāng)前的類的屬性數(shù)據(jù)大小
uint32_t unalignedInstanceSize() {
  assert(isRealized());
  return data()->ro->instanceSize;
}
//進(jìn)行內(nèi)存對(duì)齊
//WORD_MASK == 7
static inline uint32_t word_align(uint32_t x) {
   return (x + WORD_MASK) & ~WORD_MASK;
}

以下是內(nèi)存對(duì)齊的算法(參考了我是好寶寶 寫的內(nèi)容),此內(nèi)容通俗易懂灌砖。

假如: 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ù)對(duì)齊,即 8 字節(jié)對(duì)齊

作者:我是好寶寶
鏈接:https://juejin.im/post/5df6323d6fb9a016317813b0
來源:掘金
    1. 根據(jù)不同的條件基显,使用calloc或者malloc_zone_calloc進(jìn)行內(nèi)存申請(qǐng)蘸吓,并且初始化isa指針,至此size大小的對(duì)象obj已經(jīng)申請(qǐng)完成撩幽,并且返回库继。
      注:initIsa(cls),isa綁定類class這個(gè)后面再講窜醉。
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);

三制跟、init & new

    1. 我們?cè)倏匆幌?strong>init方法,其實(shí)里面沒做什么事酱虎。就提供一個(gè)工廠方法給開發(fā)者擴(kuò)展雨膨。比如我們平時(shí)寫的self == [super init],然后寫上我們自定義的方法
- (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;
}
    1. new調(diào)用的是callAlloc方法和init,那么可以理解為new實(shí)際上就是alloc+init 的綜合體读串。
+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

總結(jié):

    1. alloc 申請(qǐng)的內(nèi)在空間是以8為倍數(shù)聊记,且最小為16bit;
    1. init 沒做什么事撒妈,就為了方便開發(fā)者擴(kuò)展;
    1. new 就是alloc 和 init 的集合。

最后看一張alloc的流程圖排监,此圖根據(jù)objc的源碼經(jīng)以上分析得來狰右。

alloc 流程.png

參考:
蘋果的開源代碼
Coci老師的調(diào)試objc源碼的方法
iOS探索 alloc流程 (by我是好寶寶)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市舆床,隨后出現(xiàn)的幾起案子棋蚌,更是在濱河造成了極大的恐慌,老刑警劉巖挨队,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谷暮,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡盛垦,警方通過查閱死者的電腦和手機(jī)湿弦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腾夯,“玉大人颊埃,你說我怎么就攤上這事〉悖” “怎么了班利?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)榨呆。 經(jīng)常有香客問我罗标,道長(zhǎng),這世上最難降的妖魔是什么愕提? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任馒稍,我火速辦了婚禮,結(jié)果婚禮上浅侨,老公的妹妹穿的比我還像新娘纽谒。我一直安慰自己,他們只是感情好如输,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布鼓黔。 她就那樣靜靜地躺著,像睡著了一般不见。 火紅的嫁衣襯著肌膚如雪澳化。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天稳吮,我揣著相機(jī)與錄音缎谷,去河邊找鬼。 笑死灶似,一個(gè)胖子當(dāng)著我的面吹牛列林,可吹牛的內(nèi)容都是我干的瑞你。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼希痴,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼者甲!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起砌创,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤虏缸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后嫩实,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刽辙,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年舶赔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扫倡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谦秧。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡竟纳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疚鲤,到底是詐尸還是另有隱情锥累,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布集歇,位于F島的核電站桶略,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏诲宇。R本人自食惡果不足惜际歼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望姑蓝。 院中可真熱鬧鹅心,春花似錦、人聲如沸纺荧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宙暇。三九已至输枯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間占贫,已是汗流浹背桃熄。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留型奥,地道東北人瞳收。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓池充,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親缎讼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子收夸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 衣柜里有件香奈兒連衣裙,被我精心呵護(hù)著,唯有它套著保護(hù)套,占據(jù)著衣柜里最好的位置.這是件黑白相間經(jīng)典款的香奈兒,這...
    斯ss閱讀 314評(píng)論 0 0
  • 柳綠桃紅次第, 雙燕逐風(fēng)嬉戲血崭。 不耐曉寒輕卧惜, 坐看詩書無意。 無意夹纫,無意咽瓷, 春色侵窗愁碧。 2017.3.17
    與行有余閱讀 550評(píng)論 0 0
  • 他們跑在前面舰讹, 眼里收藏著落日的余暉茅姜。 我跟在后面, 閑得發(fā)慌地盯著路邊被風(fēng)吹動(dòng)的綠葉月匣。 他們追逐钻洒, 星月流轉(zhuǎn),流...
    公子嫣然長(zhǎng)安閱讀 123評(píng)論 0 1
  • 感冒分為普通感冒和流行性感冒锄开。普通感冒中又分為風(fēng)熱感冒素标、風(fēng)寒感冒、暑濕感冒萍悴。流行性感冒又稱流感头遭,具有傳染性。 一癣诱、...
    溫沐堇閱讀 211評(píng)論 0 1
  • 2014年12月计维,馮紹峰在拍攝《西游記之孫悟空三打白骨精》時(shí),騎乘的白馬在拍戲過程中受到驚嚇撕予,他因此從馬背上墜地鲫惶,...
    影視心觀察閱讀 2,308評(píng)論 0 0