OC底層原理01-alloc流程探索

一载荔、探索歷程

思考:從哪里開始探索? -> 對象的初始化盾饮?-> [對象 alloc]?

不管三七二十一,既然是探索alloc流程,那就先整一個alloc來玩一玩

  1. 創(chuàng)建一個GomuPerson對象


    image.png
  2. 初始化對象:通過下面一個小小的操作來引入思考
GomuPerson *p1 = [GomuPerson alloc];
GomuPerson *p2 = [p1 init];
GomuPerson *p3 = [p1 init];

//: 問題: p1,p2,p3 是相同的對象丘损,還是不同的對象普办?
//: 猜是沒有任何結(jié)果的,那我們不妨打印一下徘钥,讓結(jié)果更直觀的出現(xiàn)在我們面前衔蹲,走你~
NSLog(@"p1 : %@",p1);
NSLog(@"p2 : %@",p2);
NSLog(@"p3 : %@",p3);
//: log 如下:
//: p1 : <GomuPerson: 0x600000376a40> 
//: p2 : <GomuPerson: 0x600000376a40> 
//: p3 : <GomuPerson: 0x600000376a40>

結(jié)論:
p1,p2,p3 對象的內(nèi)存指針地址相同,既然他們的內(nèi)存指針地址相同呈础,那我們可以推斷他們是相同的對象舆驶。

思考:那指向內(nèi)存指針地址的指針地址是否相同呢?

那我們就打印一下指向?qū)ο笾羔樀刂返闹羔樀刂?/p>

NSLog(@"p1 : %p",&p1);
NSLog(@"p2 : %p",&p2);
NSLog(@"p3 : %p",&p3);
//: log 如下:
//: p1 : 0x7ffee62df138
//: p2 : 0x7ffee62df130
//: p3 : 0x7ffee62df128

結(jié)論:

  1. 指向內(nèi)存指針指針的指針地址不相同而钞。不同的指針指向了共用同一塊內(nèi)存指針地址的對象
  2. 由p1,p2,p3的指針地址推斷沙廉,棧內(nèi)存是連續(xù)的(0x30 + 0x08 = 0x38, 0x28 + 0x08 = 0x30 [這里是16進制運算]),并且所占內(nèi)存都是8字節(jié)(指針占內(nèi)存8字節(jié))
  3. init不會對我們開辟的對象的內(nèi)存空間進行修改臼节,地址指針的創(chuàng)建來自于alloc

附圖


image.png

二撬陵、開始探索

首先想到就是 command + 鼠標左鍵點擊 alloc 進入源碼,但是失敗了网缝,發(fā)現(xiàn)這部分代碼沒有開源巨税,那我們應(yīng)該該怎么辦呢?

蘋果開源庫:
1:https://opensource.apple.com/source 所以開源庫都在里面,包括各個老版本
2:https://opensource.apple.com 可以根據(jù)系統(tǒng)版本選擇更新的下載

知道哪里下載庫了粉臊,那我們怎么知道alloc屬于哪個庫呢草添?

這里為大家提供3個方法,供參考

方法一: 下符號斷點的形式直接跟流程

  1. 下符號斷點的方法


    image.png
  2. 先在我們要研究的對象GomuPerson初始化處下一個斷點


    image.png
  3. 執(zhí)行程序到斷點處
  4. 因為我們要研究alloc扼仲,所以下一個名為alloc的符號斷點


    image.png
  5. 讓程序繼續(xù)走


    image.png
  6. 得到我們想要的東西了 libobjc.A.dylib远寸,推測alloc 在這個庫中
    image.png
  7. 這里為什么走到了[NSObject alloc]方法,而不是[GomuPerson alloc]屠凶?
  • 因為GomuPerson 繼承 NSObject而晒,而且 GomuPerson 里面沒有 alloc方法

方法二:Ctrl + step into

  1. 首先在我們還是要研究的對象GomuPerson初始化處下一個斷點


    image.png
  2. 讓程序執(zhí)行到這里之后,按住Ctrl阅畴,這個下一步的按鈕就會變成如下圖所示


    image.png
  3. 按住Ctrl,多點幾次下一步迅耘,來到了 objc_alloc
    image.png
  4. objc_alloc作為符號斷點贱枣,加上的一瞬間就又找到了libobjc.A.dylib
    image.png

方法三:匯編查看跟流程(最常用的)

  1. 首先在我們還是要研究的對象GomuPerson初始化處下一個斷點


    image.png
  2. Debug -> Debug Workflow -> Always Show Disassembly 進入?yún)R編


    image.png
  3. 得到 objc_alloc
    image.png
  4. 用方法二下符號斷點又可以拿到libobjc.A.dylib

三、找到源碼庫libobjc.A.dylibobjc4

下載源碼+編譯源碼請移步到 iOS_objc4-781.2 最新源碼編譯調(diào)試

四颤专、打開源碼工程纽哥,開始alloc探索之旅

方法一: 通過 command + 鼠標左鍵 一步一步進入源碼看流程

  • 進入alloc
    image.png
  • 進入[NSObject alloc]
    image.png
  • 進入_objc_rootAlloc
    image.png
  • 進入 callAlloc
    image.png

由上面步驟我們可以先梳理一個流程如下:


image.png

那么問題來了:

問題一:進入callAlloc方法之后,是進入objc_msgSend呢栖秕,還是進入_objc_rootAllocWithZone?
問題二:我們根據(jù)代碼走查梳理出來的步驟是否準確春塌?

方法二:下符號斷點,驗證上面問題和流程

  • 我們下了如下3個符號斷點


    image.png
  • 打開Debug -> Debug Workflow -> Always Show Disassembly 進入?yún)R編然后打開上面3個斷點,通過斷點調(diào)試只壳,得到以下流程


    image.png

結(jié)論:

  1. 我們剛剛走查代碼梳理的流程錯誤俏拱?因為缺少了callAlloc這步
  2. 執(zhí)行到callAlloc方法之后,下一步執(zhí)行_objc_rootAllocWithZone吼句,沒有執(zhí)行objc_msgSend

方法三:進入源碼下斷點锅必,驗證上面結(jié)論1和2。
問題:由于 objc_alloc_objc_rootAlloc 都是調(diào)用的 callAlloc惕艳,那他們到底是怎樣調(diào)用的搞隐?是兩個都要調(diào)用嗎?那callAlloc豈不是要走2次远搪?

  • 下四個斷點如下圖


    image.png

    image.png

    image.png

    image.png
  • 經(jīng)過觀察發(fā)現(xiàn)劣纲,objc_alloc_objc_rootAlloc 的第二個入?yún)?code>checkNil不同
    image.png
  • 經(jīng)過一番斷點調(diào)試得出objc_alloc_objc_rootAlloc都會調(diào)用,callAlloc確實要走兩次谁鳍,第一次調(diào)用objc_alloc 時癞季,callAlloc方法會走最后一句return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc)),接著調(diào)用_objc_rootAlloc方法時棠耕,callAlloc方法才會調(diào)用_objc_rootAllocWithZone方法余佛,得出以下流程圖
    image.png

那么問題又來了:callAlloc被調(diào)用了兩次,為什么匯編調(diào)試的時候窍荧,一次都沒有走符號斷點呢辉巡?

這個問題我們下次單獨一期來講解,它牽涉到<編譯優(yōu)化>蕊退,有興趣的朋友可以先自行研究郊楣。

繼續(xù)探索_objc_rootAllocWithZone之后的流程

  1. 調(diào)用alloc的核心方法_class_createInstanceFromZone
    image.png
  • 經(jīng)過一系列斷點調(diào)試,發(fā)現(xiàn)_objc_rootAllocWithZone會走這三步瓤荔,那我可以開始思考净蚤?alloc既然是開辟內(nèi)存,那如果讓我們開辟內(nèi)存输硝,我們會怎么做呢谭梗?1.計算內(nèi)存大小。2.向系統(tǒng)申請內(nèi)存熊锭。3.內(nèi)存與類關(guān)聯(lián)幌衣。從上圖方法名我們也不難猜測出這3步的作用。得到以下的流程圖:
    image.png

五郎逃、拓展知識

開辟空間是怎么進行16位內(nèi)存對齊的(為什么16位哥童?哈,蘋果爸爸規(guī)定的褒翰,以前是8位)
通過一個很6的算法:(x + size_t(15)) & ~size_t(15)

//: 比如當前x 傳入8字節(jié)
8 + 15 = 23
換算成16位贮懈,2進制:
0000 0000 0001 0111
//: ~size_t(15) 先把15換算成2進制
0000 0000 0000 1111
//: 取反 并與23 & 運算
1111 1111 1111 0000
0000 0000 0001 0111
0000 0000 0001 0000 
//:  結(jié)果為16匀泊,從這個運算不難看出,15取反后后四位都是0朵你,這個算法就是抹掉后四位各聘,那就從倒數(shù)第五位開始運算,則結(jié)果都為16倍數(shù)

init new 做了什么事撬呢?

  • init
//: init源碼
- (id)init {
    return _objc_rootInit(self);
}
_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;
}
//: 從上面源碼不難看出伦吠,init什么也沒做,就返回了自己
結(jié)論:init的作用:構(gòu)造方法魂拦,也是一種工廠設(shè)計毛仪,方便開發(fā)者重新,給開發(fā)者提供相應(yīng)入口芯勘,比如:initWithFrame
  • new
//: new源碼
+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}
//: [GomuPerson new] 就相當于 [[GomuPerson alloc] init]

結(jié)論:經(jīng)過一番斷點調(diào)試箱靴,發(fā)現(xiàn)[GomuPerson new]和第一次GomuPerson *p = [GomuPerson alloc]的流程一模一樣,所以 new:相當于(alloc + init)

GomuPerson *p = [GomuPerson alloc];
GomuPerson *p1 = [GomuPerson alloc];
再初始化一個對象p1荷愕,會發(fā)現(xiàn)p1的流程又變了衡怀,如圖:

image.png
結(jié)論:第二次初始化相同對象的時候,不會再走_objc_rootAlloc這個流程安疗。

那為什么呢抛杨?

NSObject *obj = [NSObject alloc];
和上面初始化p1的流程一樣
問題:為什么p1和obj都不走_objc_rootAlloc呢?

  • 先回答第一個問題:為什么[NSObject alloc]怖现,不走_objc_rootAlloc方法:
  1. 在main函數(shù)之前打個斷點


    image.png
  2. 添加一個alloc的符號斷點
    image.png
結(jié)論:原來進入main函數(shù)之前,_objc_rootAlloc函數(shù)已經(jīng)被調(diào)用了玉罐,就相當于初始化p的流程屈嗤,所以后面我們再次初始化[NSObject alloc]的時候,就和初始化p1的流程一樣了吊输。
  1. 然后我們?nèi)∠?code>main函數(shù)的斷點
  2. 添加一個objc_alloc的符號斷點
    image.png

    發(fā)現(xiàn)系統(tǒng)第一個調(diào)用的是NSArray
    image.png

    結(jié)論:NSArray繼承于NSObject饶号,系統(tǒng)初始化NSArray的時候,會調(diào)用callAlloc方法里的msgSend季蚂,由于NSArray沒有alloc方法茫船,所以這個消息會發(fā)送給根父類NSObject,得出NSObject的初始化方法由系統(tǒng)幫我們執(zhí)行了扭屁。
  • 第二個問題:為什么+ (id)alloc {}下面的方面明明是_objc_rootAlloc透硝,它為什么跑去調(diào)用objc_alloc
    進入LLVM開源代碼疯搅,搜索objc_alloc會找到一個關(guān)鍵方法
    image.png
結(jié)論:調(diào)用alloc方法,在LLVM層(在編譯啟動就已完成)埋泵,對alloc進行了修飾幔欧,會指向objc_alloc方法
  • 第三個問題:那為什么執(zhí)行完objc_alloc方法后罪治,又會執(zhí)行一次alloc -> _objc_rootAlloc方法呢?
  1. 第一次調(diào)用objc_alloc方法礁蔗,會執(zhí)行到msgSend觉义,進入LLVM源碼查看
    image.png
  2. 第一次是調(diào)用if里面的條件判斷,執(zhí)行tryGenerateSpecializedMessageSend,傳入的Selobjc_alloc浴井,所以程序第一次走了objc_alloc晒骇。
  3. 返回一個NO,接著執(zhí)行下面的GenerateMessageSend磺浙,傳入的Selalloc洪囤,所以第二次走到了alloc -> _objc_rootAlloc
  • 第四個問題: 第二次初始化GomuPerson對象流程 alloc -> objc_alloc -> callAlloc -> _objc_rootAllocWithZone 為什么第二次初始化對象的時候就不調(diào)用_objc_rootAlloc了呢?
  1. 先回顧一下第一次初始化對象的流程 alloc -> objc_alloc -> callAlloc -> objc_msgSend -> alloc -> _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone
  2. 對比第一次和第二次發(fā)現(xiàn)撕氧,第一次調(diào)用 objc_alloc 走到了 callAllocobjc_msgSend方法瘤缩,第一次調(diào)用 objc_alloc 走到了 callAlloc_objc_rootAllocWithZone方法。
  3. 差異在哪伦泥?第二次初始化對象剥啤,不會調(diào)用 objc_msgSend 方法。
  4. 那我們現(xiàn)在探索就變成 objc_msgSend 方法做了什么不脯。
  5. objc_msgSend 就是通過 sel 去找 Imp府怯,找到之后cache,既然有了cache防楷,那我們第二次初始化GomuPerson對象的時候牺丙,就直接可以進入快速查詢,直接找cache域帐。(該部分內(nèi)容屬于<OC方法底層原理>赘被,后面我們會專門開一期來闡述)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肖揣,一起剝皮案震驚了整個濱河市民假,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌龙优,老刑警劉巖羊异,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異彤断,居然都是意外死亡野舶,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門宰衙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來平道,“玉大人,你說我怎么就攤上這事供炼∫晃荩” “怎么了窘疮?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長冀墨。 經(jīng)常有香客問我闸衫,道長,這世上最難降的妖魔是什么诽嘉? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任蔚出,我火速辦了婚禮,結(jié)果婚禮上虫腋,老公的妹妹穿的比我還像新娘骄酗。我一直安慰自己,他們只是感情好岔乔,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布酥筝。 她就那樣靜靜地躺著,像睡著了一般雏门。 火紅的嫁衣襯著肌膚如雪嘿歌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天茁影,我揣著相機與錄音宙帝,去河邊找鬼。 笑死募闲,一個胖子當著我的面吹牛步脓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播浩螺,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼靴患,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了要出?” 一聲冷哼從身側(cè)響起鸳君,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎患蹂,沒想到半個月后或颊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡传于,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年囱挑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沼溜。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡平挑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出系草,到底是詐尸還是另有隱情弹惦,我是刑警寧澤否淤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站棠隐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏檐嚣。R本人自食惡果不足惜助泽,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嚎京。 院中可真熱鬧嗡贺,春花似錦、人聲如沸鞍帝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帕涌。三九已至摄凡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蚓曼,已是汗流浹背亲澡。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纫版,地道東北人床绪。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像其弊,于是被迫代替她去往敵國和親癞己。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353