在oc中創(chuàng)建對(duì)象的時(shí)候,經(jīng)常會(huì)用到這樣一句代碼
NSObject *o = [[NSObject alloc]init];
為什么要alloc和init呢?alloc 和init 分別做了什么事情呢?通俗來說,alloc是申請(qǐng)分配內(nèi)存地址,init是將地址分配給對(duì)應(yīng)的指針。那具體是怎么做的,暫時(shí)不得而知。
首先來看一個(gè)小小的例子
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [p1 init];
LGPerson *p3 = [p1 init];
NSLog(@"%@-%p-%p",p1,p1,&p1);
NSLog(@"%@-%p-%p",p2,p2,&p2);
NSLog(@"%@-%p-%p",p3,p3,&p3);
以下是打印結(jié)果
<LGPerson: 0x600003d10510>-0x600003d10510-0x16f497968
<LGPerson: 0x600003d10510>-0x600003d10510-0x16f497960
<LGPerson: 0x600003d10510>-0x600003d10510-0x16f497958
可以看到,不同的init(p2,p3)都指向了同一個(gè)alloc開辟的內(nèi)存地址,而指針的地址是不同的。那p1的地址是怎么得到呢,其實(shí)p1也是一個(gè)分配到的指針地址,和p2、p3是一樣的。這就說明創(chuàng)建對(duì)象的時(shí)候,init只是影響這個(gè)指針的地址,而不影響對(duì)象實(shí)際的內(nèi)存地址。那么重點(diǎn)就是alloc了,可是alloc是系統(tǒng)的方法,只能看到其聲明,不能看到其實(shí)現(xiàn)。其實(shí)蘋果已經(jīng)開源了oc的一部份底層源碼,傳送門:源碼鏈接
在這眾多的底層源碼中我們要去找那個(gè)底層源碼呢,不能大海撈針吧,所以還是需要一點(diǎn)點(diǎn)的技巧,首先回到Xcode我們的代碼中對(duì) LGPerson *p1 = [LGPerson alloc]; 這一行代碼打一個(gè)斷點(diǎn)。
運(yùn)行工程,在斷點(diǎn)到達(dá)之后通過

符號(hào)斷點(diǎn)添加alloc,點(diǎn)擊斷點(diǎn)下一步即可進(jìn)入?yún)R編調(diào)試,在此處可以看到alloc方法跳轉(zhuǎn)到libobjc.A.dylib 庫中的 _objc_rootAlloc的方法里面

同樣,也可以開啟此選項(xiàng),也能進(jìn)入?yún)R編調(diào)試的界面

OK,來到上面?zhèn)魉烷T的地址,搜索objc,即可看到蘋果已經(jīng)開源了此源碼,下載對(duì)應(yīng)的源碼即可,此處使用的源碼版本為objc4-866.9
得到了源碼之后,還不能直接調(diào)試,會(huì)報(bào)很多的錯(cuò)誤,依照網(wǎng)上眾多的如何編譯文章,即可得到源碼的調(diào)試版本
可以使用以下Cooci大神已經(jīng)完善的可調(diào)試版本,傳送門:可調(diào)試版本
可以調(diào)試之后,在回到alloc的探索,同樣些許 alloc方法,點(diǎn)擊進(jìn)入,會(huì)看到_objc_rootAlloc(self) 方法,同樣一路跟隨,直到

走哪里?這就是為什么要將源碼編譯,直接在return的地方打上斷點(diǎn)。一步步的運(yùn)行,直到_class_createInstanceFromZone 方法中??梢钥吹?br> alloc -> _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone -> _class_createInstanceFromZone 創(chuàng)建好Zone, 利用zone重復(fù)之前的步驟創(chuàng)建內(nèi)存。
那申請(qǐng)內(nèi)存的大小是多少呢,
_class_createInstanceFromZone方法中,返回的obj 在創(chuàng)建的時(shí)候malloc_zone_calloc 方法,傳入了一個(gè)size,malloc_zone_calloc 是屬于系統(tǒng)另外一個(gè)源碼中的,此時(shí)先不用關(guān)注,其中存在size = cls->instanceSize(extraBytes); 用參數(shù)cls調(diào)用instanceSize這個(gè)方法,
inline 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;
}
以上的方法,其實(shí)已經(jīng)說明了,alloc一個(gè)對(duì)象的時(shí)候最少需要分配16 bytes的空間,如果還有額外的空間,則使用alignedInstanceSize() + extraBytes,alignedInstanceSize()返回的正是16 bytes,具體的算法為8字節(jié)對(duì)齊,關(guān)于結(jié)構(gòu)體字節(jié)對(duì)齊及對(duì)象內(nèi)存對(duì)齊請(qǐng)看:iOS的內(nèi)存對(duì)齊
橋的麻袋,為什使用了8字節(jié)對(duì)齊,而一個(gè)對(duì)象的的初始值是16bytes,這里需要理解對(duì)象的本質(zhì),簡(jiǎn)單來說,每一個(gè)對(duì)象都存在一個(gè)isa,isa占據(jù)了8個(gè)字節(jié),那么這個(gè)對(duì)象就是8個(gè)字節(jié)嘛,沒錯(cuò),這個(gè)對(duì)象就是8個(gè)字節(jié),可是對(duì)象也需要存儲(chǔ)數(shù)據(jù),如果這個(gè)對(duì)象不存儲(chǔ)數(shù)據(jù),那么存在的意義就沒有了,由此可知,每個(gè)對(duì)象的都是大于等于8的,而等于8沒有意義,所以最小的則為16bytes。
而malloc_zone_calloc 方法

在這里可以看到此方法屬于另外源碼,所以依葫蘆畫瓢,符號(hào)斷點(diǎn)找到libsystem_malloc.dylib,同樣可以在開源的網(wǎng)站中找到。至于更深的原理可以嘗試研究system庫。
所以alloc就這么簡(jiǎn)單?其實(shí)使用真機(jī)調(diào)試的時(shí)候是不會(huì)走此流程的,因?yàn)閍lloc方法是可以申請(qǐng)系統(tǒng)的內(nèi)存空間的,所以蘋果也考慮到此方法比較特殊,使用真機(jī)調(diào)試的時(shí)候,在匯編調(diào)試的界面,可以看到的是

bl跳轉(zhuǎn)到objc_alloc方法里面了,此為llvm做的優(yōu)化,其實(shí)如果在main函數(shù)這一行打斷點(diǎn)即可發(fā)現(xiàn),在運(yùn)行main之前,會(huì)加載很多的動(dòng)態(tài)庫,而llvm做的就是利用runtime機(jī)制,使用objc_alloc替換掉_objc_rootAlloc。
提示:
調(diào)試源碼如果再次運(yùn)行報(bào)錯(cuò)
rosetta error: /var/db/oah/2fb2317b534e6e96aaea62b820773e0d72e8e5e936ef74c075859ac0f8147f82/f9ad793f5722c467a7579d81a8f75c92233018e4ed08d5ba5464d53762b8f802/libobjc.A.dylib.aot: attachment of code signature supplement failed: 1
Message from debugger: Terminated due to signal 5
Program ended with exit code: 5
刪除product本地生成的debug文件夾即可。