1)了解OC運(yùn)行底層入口
通常是直接進(jìn)入main函數(shù),通過插入斷點(diǎn),在工作臺運(yùn)行bt命令,可以得知線程調(diào)用狀態(tài),如下:

為了進(jìn)一步了解,在進(jìn)入main之前所做的事情,插入符號斷點(diǎn),可以看到start時(shí),調(diào)用到了libdyld.dylib,如下:

同理,可以查看到在dylb_start之前還有l(wèi)ibSystem.B.dylib(libSystem_initializer,連接系統(tǒng)函數(shù)庫),libdispatch.dylib(libdispatch_init,對應(yīng)系統(tǒng)GCD),libobjc.A.dylib(_objc_init,對應(yīng)底層runtime),如下:

進(jìn)入main函數(shù)之后,通過命令查看全貌,如下:

由此引出底層探索的范圍,除了dylib啟動自動加載動態(tài)庫,可能還涉及到libsystem、類(分類/方法/協(xié)議/屬性/對象)、runtime、runloop、kvc、kvo等。
2)了解對象alloc與init
對象通過alloc申請內(nèi)存之后,打印出對象、對象指針、以及所對應(yīng)的指針地址,可以看到三個(gè)對象p1、p2、p3,對象與指針內(nèi)容所對應(yīng)的地址是完全相同的,因?yàn)樗麄兌贾赶蛲粔K內(nèi)存區(qū)域;但是,三個(gè)對象打印出的指針,所在地址是不同的,因?yàn)榇鎯χ羔樥加玫目臻g由系統(tǒng)內(nèi)存統(tǒng)一分配,此處要區(qū)分出與指針指向內(nèi)容是不同的。

簡單示意如下:

通過觀察進(jìn)一步發(fā)現(xiàn),p1、p2、p3內(nèi)存相差8,這就涉及到內(nèi)存分配??臻g連續(xù),以及16字節(jié)對齊問題(以往是8字節(jié)對齊,目前都是16字節(jié))。
3)定位alloc源碼庫
蘋果開源源碼地址:https://opensource.apple.com/,其中objc更新地址為:https://opensource.apple.com/source/objc4/。

截止目前,mac os 10.15.5對objc有更新版本對應(yīng)如下,但是還不穩(wěn)定,所以本文采用的objc-781版本:

有三種方式查找對應(yīng)alloc源碼庫,依次如下:
[1]利用蘋果開源源碼,插入符號斷點(diǎn)進(jìn)行分析

[2]插入符號斷點(diǎn)obj_alloc,編譯器提示

[3]運(yùn)行導(dǎo)對象斷點(diǎn)處,按住ctrl,step in,點(diǎn)擊Debug菜單,Debug workflow-always show disassembly,打開調(diào)用匯編(FIXME:后面如果是通過調(diào)試源碼驗(yàn)證,此處務(wù)必取消勾選,否則無法進(jìn)入到objc源碼中,本人因?yàn)檫@個(gè)問題耗費(fèi)了兩天時(shí)間,才找到原因,直接跟匯編的大神可忽略!),如下:

4)alloc源碼分析
打開官方下載到的objc源碼,直接搜索 『alloc {』,即可定位到,如下:

可以看到alloc調(diào)用的正是_objc_rootAlloc,這與dylib庫查看到的一致。通過跳轉(zhuǎn)可以總結(jié)出調(diào)用鏈,如下:

由此可知,真正分配內(nèi)存的關(guān)鍵函數(shù)是_class_createInstanceFromZone,這個(gè)函數(shù)包含如下幾個(gè)關(guān)鍵步驟:

備注:zone是老系統(tǒng)的分配方式,目前已經(jīng)基本舍棄!
下面需要調(diào)試以驗(yàn)證執(zhí)行流程,在此之前配置源碼,參考https://juejin.im/post/6844904082226806792,具體步驟分析:
[1] 內(nèi)存分配 instanceSize

通過運(yùn)行實(shí)例發(fā)現(xiàn),extraBytes=0,并且進(jìn)入fastpath分支(x大概率不為0,標(biāo)識編譯器優(yōu)化,對應(yīng)的還有slowpath,大概率為0,簡單標(biāo)識優(yōu)化處理),即fastInstanceSize調(diào)用,接下來了解這個(gè)調(diào)用具體邏輯:

其中,_flags表示當(dāng)前類標(biāo)識(類似nonpointerisa),與x64的內(nèi)存優(yōu)化有關(guān),另外還有兩個(gè)用于字節(jié)對齊的mask,F(xiàn)AST_CACHE_ALLOC_MASK = 0x1ff8,F(xiàn)AST_CACHE_ALLOC_DELTA16 = 0x0008(新版本才有對應(yīng)介紹),通過計(jì)算size=40,然后返回調(diào)用align16進(jìn)行對齊(入?yún)ⅲ?0 + 0 - 8 = 32),具體如下:

通過以上計(jì)算,得到了分配內(nèi)存的實(shí)際size = 32。至于內(nèi)存對齊,主要是為了系統(tǒng)訪問對象(至少isa會占用8字節(jié))提供便利,類似固定的『滑動窗口』。
在控制臺執(zhí)行內(nèi)存查看命令『x』,正是32字節(jié)分配,內(nèi)容地址如下:

執(zhí)行『x/4g』以每8字節(jié)格式化展示,由于ios在內(nèi)存分配上采用的小端對齊,所以從上到下打印,第一個(gè)地址0x001d80010000226d表示isa,直接打印是無效的,需要結(jié)合mask,第2~4各個(gè)屬性打印如下:

[2]?calloc開辟內(nèi)存
通過調(diào)用obj = (id)calloc(1, size)開辟內(nèi)存,拿到對應(yīng)的指針地址,具體定義如下:

[3]initIsa內(nèi)存與isa綁定

[4]object_cxxConstructFromClass執(zhí)行對象構(gòu)造,發(fā)送msg消息,至此alloc流程全部走完。

5)alloc調(diào)用流程圖

6)init和new作用與區(qū)別
init作用:從定義可以看出,其是為用戶提供構(gòu)造方法,進(jìn)行重寫初始化,屬于工廠設(shè)計(jì)模式。

new作用:從定義可以看出,原理等同于alloc init,但是new不會調(diào)用到用戶重寫的init方法,而是直接調(diào)用系統(tǒng)的,所以用戶自定義的初始化操作就被漏掉了,因此不建議直接new。

7)NSObect調(diào)用alloc不會進(jìn)入實(shí)際內(nèi)存分配
通過執(zhí)行NSObject*objc? = [NSObject alloc];,發(fā)現(xiàn)沒有進(jìn)入alloc?
定義:NSObject只是『充當(dāng)對象』,并非類似于LGPerson的對象。

????通過調(diào)試程序,發(fā)現(xiàn)NSObject并未先調(diào)用alloc,而是直接調(diào)用objc_alloc,走完msg_send流程;然而,NSObject在進(jìn)入alloc之前,第一個(gè)系統(tǒng)執(zhí)行的cls=NSAarray,此時(shí)由于msg_send時(shí),查找不到alloc方法,所以會通知子類進(jìn)行查找;NSArray的父類是NSObject,由此可知main函數(shù)中,NSObject在執(zhí)行alloc之前,NSObject初始化已經(jīng)由系統(tǒng)執(zhí)行完成了。


????當(dāng)LGPerson第一次alloc進(jìn)入時(shí),對應(yīng)的cls是LGPerson,系統(tǒng)通過LLVM消息轉(zhuǎn)發(fā)(如上圖所示),任何調(diào)用alloc方法的類,先轉(zhuǎn)去try調(diào)用obj_alloc,之后又回到了子類的alloc,如圖『LGPerson alloc完整調(diào)用鏈』紅色部分所示,這也就解釋了為何LGPerson會調(diào)用兩次alloc的原因。