alloc與init探索

知識點概要

引入
alloc干了什么聊疲?
為什么要有init茬底?
為什么要有new?直接使用有何缺陷获洲?
[NSObject alloc]為什么沒有進(jìn)入alloc方法阱表?

引入

    Person *p1 = [[Person alloc]init];
    Person *p2 = [p1 init];
    Person *p3 = [p1 init];
    NSLog(@"%@ - %p - %p",p1,p1,&p1);
    NSLog(@"%@ - %p - %p",p2,p2,&p2);
    NSLog(@"%@ - %p - %p",p3,p3,&p3);

p1、p2贡珊、p3結(jié)果相同嗎最爬?&p1、&p2门岔、&p3結(jié)果相同嗎爱致?
答案是:前者相同,后者不同寒随,輸出結(jié)果如下:


結(jié)果.png

我們來回顧C語言指針的知識:

    int temp = 3;//定一個int類型變量
    int *a = &temp;//聲明一個int類型的指針仇轻,指向的地址是temp的內(nèi)存地址
    //直接輸出a和直接輸出&temp的結(jié)果一樣,都是temp的內(nèi)存地址萤晴,而&a表示的是a的內(nèi)存地址载碌,因為指針類型也是一個變量,需要空間去存儲
    printf("%p,%p,%p\n",a,&a,&temp);

那么蒲讯,對于&p1忘朝、&p2、&p3結(jié)果不相同就可以明白了判帮,話不多說局嘁,上圖:


指針.png

至于p1溉箕、p2、p3調(diào)用init之后結(jié)果相同悦昵,是接下來要探索的問題肴茄。

alloc干了什么?

怎么查看但指?

1.下符號斷點
1)我們知道會調(diào)用alloc方法寡痰,那么,先打上alloc符號斷點


ialloc.png

可以看到棋凳,會調(diào)用NSObject類中的alloc方法拦坠,因為我們自定義的類繼承自NSObject
2)在上一步中,可以看到j(luò)mp指令剩岳,會進(jìn)入_objc_rootAlloc贞滨,所以,繼續(xù)為_objc_rootAlloc打上斷點


_objc_rootAlloc.png

3)繼續(xù)按照之前的方式拍棕,符號斷點_objc_rootAllocWithZone
_objc_rootAllocWithZone.png

4)可以看到會調(diào)用callq指令晓铆,調(diào)用了runtime中的instanceSize、calloc方法
calloc.png

調(diào)用instanceSize方法绰播,計算需要開辟多大的內(nèi)存骄噪,接著會調(diào)用calloc開辟這樣一個大小的空間。最后蠢箩,會調(diào)用initInstanceIsa方法链蕊,將對象與這個空間關(guān)聯(lián)

2.根據(jù)符號斷點查找?guī)欤缓竺γⅲヌO果開源庫下載源碼示弓,看源碼,下面介紹三種方法查看呵萨。
1)第一種就是符號斷點
2)在你要進(jìn)入的位置奏属,打上斷點,然后潮峦,按住control鍵囱皿,點擊調(diào)試按鈕的“step into”,多點幾次忱嘹,會跳入另外一個斷點

debug.png

step into.png

打上符號斷點嘱腥,點擊下一步,如下圖拘悦,我們就知道庫名是objc
objc_alloc.png

3)打上斷點齿兔,debug選為匯編模式,找到對應(yīng)的方法objc_alloc,打符號斷點
方法.png

匯編代碼.png

總結(jié):
1.通過符號斷點分苇,可以看到alloc的流程是:
alloc-->_objc_rootAlloc-->_objc_rootAllocWithZone-->retrun obj
_objc_rootAllocWithZone方法會調(diào)用核心的三個方法:
instanceSize:計算空間大小添诉,并進(jìn)行16字節(jié)對齊
calloc:根據(jù)大小,開辟一個這樣的內(nèi)存空間
initInstanceIsa:將開辟的內(nèi)存空間與對象關(guān)聯(lián)

注意:上述的三個核心方式其實不在_objc_rootAllocWithZone里面調(diào)用医寿,而是在_class_createInstanceFromZone方法中調(diào)用的栏赴,原因稍后再說。

2.通過源碼靖秩,調(diào)用流程如下:
alloc—>_objc_rootAlloc—>callAlloc—>_objc_rootAllocWithZone-->_class_createInstanceFromZone
可以看到须眷,callAlloc、_class_createInstanceFromZone沒有被調(diào)用沟突。因為在編譯時花颗,編譯器會根據(jù)指令進(jìn)行優(yōu)化,直接進(jìn)入_objc_rootAllocWithZone


callAlloc.png

從代碼及注釋中都可以看到惠拭,調(diào)用時捎稚,會被優(yōu)化
3.一個對象的大小,與其屬性有關(guān)求橄,屬性越多,占用的空間越多葡公,且按照16自字節(jié)的方式對齊(現(xiàn)在的新版本都是16字節(jié)對齊罐农,之前是8字節(jié)對齊。為什么要對齊催什?讀取按照一定字節(jié)讀取涵亏,不去計算,會更加快速安全)蒲凶。注意气筋,沒有屬性的對象,最小占用16字節(jié)大小旋圆,

init干了什么宠默?為什么要有init?

我們直接上源碼


init.png

_objc_rootInit.png

可以看到灵巧,直接將對象返回搀矫,啥也沒有做,不是我們想的會初始化空間值刻肄。

為什么要有init瓤球?

1.提供一個方法,對外給開發(fā)者使用敏弃,可以讓開發(fā)者定義自己個性的初始化內(nèi)容卦羡。
2.單一職責(zé)原則,與開辟內(nèi)存空間功能分開

為什么要有new?有何缺陷绿饵?
new是一個類方法欠肾,調(diào)用簡單,實際是alloc+init蝴罪。

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

缺陷是:我們自定義了init董济,使用new不會調(diào)用我們自定義的init方法,造成一個對象的創(chuàng)建出問題要门。例如

- (instancetype)initWithName:(NSString *)name
{
    self = [super init];
    
    if (self) {
        self.className = name;
    }
    
    return self;
}

探討[NSObject alloc]為什么沒有進(jìn)入alloc方法虏肾?

其實,無論是NSObject還是它的子類欢搜,調(diào)用alloc或者[[XXX alloc]init],優(yōu)先調(diào)用以下代碼:

// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}

// Calls [[cls alloc] init].
id
objc_alloc_init(Class cls)
{
    return [callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/) init];
}

那么封豪,我么進(jìn)入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 __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));
}

我們知道,現(xiàn)在是objc2.0炒瘟,所以會進(jìn)入第一個分支吹埠。首先不為nil,其次調(diào)用cls->ISA()->hasCustomAWZ()疮装,看源碼:

#if FAST_CACHE_HAS_DEFAULT_AWZ
    bool hasCustomAWZ() const {
        return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
    }
    void setHasDefaultAWZ() {
        cache.setBit(FAST_CACHE_HAS_DEFAULT_AWZ);
    }
    void setHasCustomAWZ() {
        cache.clearBit(FAST_CACHE_HAS_DEFAULT_AWZ);
    }
#else
    bool hasCustomAWZ() const {
        return !(bits.data()->flags & RW_HAS_DEFAULT_AWZ);
    }
    void setHasDefaultAWZ() {
        bits.data()->setFlags(RW_HAS_DEFAULT_AWZ);
    }
    void setHasCustomAWZ() {
        bits.data()->clearFlags(RW_HAS_DEFAULT_AWZ);
    }
#endif

判斷本類是有默認(rèn)alloc/allocWithZone方法缘琅。很顯然,NSObject實現(xiàn)了廓推,所以刷袍,直接調(diào)用_objc_rootAllocWithZone方法創(chuàng)建了對象。當(dāng)然了樊展,如果是子類呻纹,而子類沒有默認(rèn)alloc/allocWithZone方法,則會走消息發(fā)送objc_msgSend专缠,調(diào)用alloc方法雷酪,源碼如下:

+ (id)alloc {
    return _objc_rootAlloc(self);
}

// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

走消息發(fā)送,最終會調(diào)用父類NSObject的alloc方法涝婉。此時哥力,流程和之前流程一樣,callAlloc->_objc_rootAllocWithZone

總結(jié)

1.為什么明明調(diào)用是alloc方法嘁圈,實際調(diào)用的是objc_alloc方法省骂?此時此刻,不要在心里一萬頭草泥馬飄過最住,冷靜下來钞澳。自己沒有更改,源碼中也沒有更改涨缚,那么轧粟,答案就只有一種了策治,編譯器做了優(yōu)化。在這里可以在LLVM源碼中找到答案兰吟,具體過程就不說了通惫,太難了,知道就行混蔼。
2.在不考慮優(yōu)化的情況下履腋,alloc的詳細(xì)流程如下:


alloc詳細(xì)流程.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市惭嚣,隨后出現(xiàn)的幾起案子遵湖,更是在濱河造成了極大的恐慌,老刑警劉巖晚吞,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件延旧,死亡現(xiàn)場離奇詭異,居然都是意外死亡槽地,警方通過查閱死者的電腦和手機迁沫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捌蚊,“玉大人集畅,你說我怎么就攤上這事∶逶悖” “怎么了牡整?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長溺拱。 經(jīng)常有香客問我,道長谣辞,這世上最難降的妖魔是什么迫摔? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮泥从,結(jié)果婚禮上句占,老公的妹妹穿的比我還像新娘。我一直安慰自己躯嫉,他們只是感情好纱烘,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著祈餐,像睡著了一般擂啥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帆阳,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天哺壶,我揣著相機與錄音,去河邊找鬼。 笑死山宾,一個胖子當(dāng)著我的面吹牛至扰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播资锰,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼敢课,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了绷杜?” 一聲冷哼從身側(cè)響起直秆,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎接剩,沒想到半個月后切厘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡懊缺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年疫稿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹃两。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡遗座,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出俊扳,到底是詐尸還是另有隱情途蒋,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布馋记,位于F島的核電站号坡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏梯醒。R本人自食惡果不足惜宽堆,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茸习。 院中可真熱鬧畜隶,春花似錦、人聲如沸号胚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽猫胁。三九已至箱亿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弃秆,已是汗流浹背极景。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工察净, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人盼樟。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓氢卡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親晨缴。 傳聞我的和親對象是個殘疾皇子译秦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355