筆記-OC對(duì)象的本質(zhì)
課堂引入
Q:p1和p2是同一個(gè)對(duì)象嗎稚配?
A:從打印結(jié)果看,顯然p1和p2指向同一塊內(nèi)存地址
Q:同一塊內(nèi)存地址就一定是同一個(gè)對(duì)象嗎姜性?那么給p賦值再看
A:顯然赘风,p1和p2是同一個(gè)對(duì)象。且可以看出init方法并沒(méi)有做什么仁卷,可以直接去掉穴翩,即alloc后p已經(jīng)可以正常使用。那么alloc方法到底做了什么锦积?
A:跳轉(zhuǎn)到alloc的方法看一下芒帕,先去看一下官方文檔中alloc方法的實(shí)現(xiàn)
+ (id)alloc {
return _objc_rootAlloc(self);
}
//-----------------------------------------------------------
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
//-----------------------------------------------------------
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));
}
看下實(shí)際調(diào)用代碼的步驟,先在Person *p = [Person alloc];
打個(gè)斷點(diǎn)丰介,跳轉(zhuǎn)到22行objc_alloc
打個(gè)symbolic breakpoint(sb) :alloc看一下alloc中的匯編實(shí)現(xiàn)
利用寄存器打印x0背蟆,確認(rèn)是Person對(duì)象
點(diǎn)擊向下按鈕,進(jìn)入_objc_rootAlloc
函數(shù)的匯編里
在_objc_rootAlloc
中并沒(méi)有看到b
跳轉(zhuǎn)到源代碼callAlloc
函數(shù)中哮幢,這里是因?yàn)榫幾g器優(yōu)化掉一次函數(shù)調(diào)用带膀,直接使用callAlloc
的下級(jí)函數(shù)_objc_rootAllocWithZone
的代碼。
使用向下箭頭跳轉(zhuǎn)到ojbc_rootAllocWithZone
里橙垢,找到23行ret指令垛叨,跳轉(zhuǎn)到23行
ret指令表示return,此處返回的是個(gè)指針柜某,使用寄存器命令register read x0
在lldb中查看x0存儲(chǔ)的參數(shù)嗽元,po
打印出來(lái)是Person對(duì)象敛纲,說(shuō)明創(chuàng)建了一個(gè)Person對(duì)象,說(shuō)明alloc才是真正創(chuàng)建實(shí)例對(duì)象的方法还棱,oc對(duì)象本身就是個(gè)結(jié)構(gòu)體指針载慈。
接下來(lái)看看init做了什么?
斷點(diǎn)到init方法珍手,直接看30行objc_msgSend
办铡,利用register查看x8,發(fā)現(xiàn)調(diào)用的是init方法
設(shè)置symbolic breakpoint: init斷點(diǎn)琳要,進(jìn)入init寡具,發(fā)現(xiàn)直接ret返回
查看oc源碼,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;
}
發(fā)現(xiàn)init確實(shí)沒(méi)做什么直接返回對(duì)象稚补,所以其實(shí)繼承NSObject后alloc就可以使用對(duì)象了童叠。init是留給開(kāi)發(fā)者進(jìn)行重寫(xiě)的方法。
拓展知識(shí)點(diǎn)
寄存器的指令跟硬件有關(guān)课幕,5s以后都是ARM64架構(gòu)
64位厦坛? 32位?和CPU有關(guān)-->數(shù)據(jù)吞吐量
一根電線(xiàn) 1個(gè)bit
32根電線(xiàn) 4個(gè)字節(jié)
64根電線(xiàn) 8個(gè)字節(jié)
理論上64位的效率是32位的2倍
真機(jī)debug乍惊,匯編知識(shí)點(diǎn)
- bl指令:跳轉(zhuǎn)杜秸,調(diào)用函數(shù)
- 寄存器,在A(yíng)RM64中有32個(gè)润绎,用來(lái)暫存數(shù)據(jù)撬碟。X0-X7存放函數(shù)的參數(shù),如
objc_sendMsg(self, SEL)
中self存放在X0莉撇,SEL存放在X1呢蛤。lldb調(diào)試中使用register read x1
命令查看寄存器x1內(nèi)容 - b相當(dāng)于bl,ret相當(dāng)于return
編譯器優(yōu)化 棍郎,跳過(guò)bl到下級(jí)函數(shù)其障,直接使用下級(jí)函數(shù)的代碼,優(yōu)化掉函數(shù)調(diào)用涂佃。bl需要訪(fǎng)問(wèn)內(nèi)存静秆,影響效率。編譯器優(yōu)化對(duì)應(yīng)Build Settings中的Optimization Level設(shè)置巡李。
演示Optimization Level設(shè)置
正常debug模式下抚笔,上面QA環(huán)節(jié)中debug模式配置也是如下,w0=1侨拦,w1=2殊橙,調(diào)用sum函數(shù)
w0和w1寄存器比x0和x1寄存器短,是32位的,有4個(gè)字節(jié)32bit位膨蛮,用來(lái)節(jié)約性能叠纹。如傳遞int型,在64位中占用4個(gè)字節(jié)32bit敞葛,需要w寄存器就可以誉察。
修改Debug的編譯器優(yōu)化
發(fā)現(xiàn)匯編代碼量減少,且sum函數(shù)已經(jīng)被優(yōu)化掉了惹谐,直接算出結(jié)果w8=3持偏,這就是編譯器優(yōu)化。
alloc到底是怎么創(chuàng)建對(duì)象的氨肌?鸿秆?
查詢(xún)oc源碼alloc最終實(shí)現(xiàn)在_class_createInstanceFromZone
函數(shù)
size算出要初始化對(duì)象的大小,其中extraBytes傳入的是0
說(shuō)明一個(gè)oc對(duì)象最小占用16個(gè)字節(jié)
word_align實(shí)現(xiàn)==>字節(jié)對(duì)齊:內(nèi)存空間按照Byte字節(jié)劃分怎囚,理論上任何數(shù)據(jù)的訪(fǎng)問(wèn)可以從任何的地址值開(kāi)始卿叽,實(shí)際上訪(fǎng)問(wèn)特殊類(lèi)型時(shí)經(jīng)常進(jìn)行空間排列。例如
內(nèi)存地址 | 數(shù)據(jù)類(lèi)型 |
---|---|
001 | short |
002 | |
003 | |
004 | |
005 | int |
006 | int |
007 | int |
008 | int |
一個(gè)short數(shù)據(jù)占據(jù)一個(gè)字節(jié)在001恳守,假設(shè)有一個(gè)int數(shù)據(jù)考婴,它在內(nèi)存中占用4個(gè)字節(jié)。因?yàn)橛布?wèn)題催烘,int數(shù)據(jù)并不是緊挨著short類(lèi)型排列在002沥阱,可能是在005。CPU在訪(fǎng)問(wèn)時(shí)颗圣,如果是按照4個(gè)字節(jié)來(lái)訪(fǎng)問(wèn)喳钟,int按照這樣對(duì)齊放置在005-008的話(huà)訪(fǎng)問(wèn)沒(méi)問(wèn)題屁使≡谄瘢可是如果int型放在004-007,CPU先訪(fǎng)問(wèn)001-004獲取部分int數(shù)據(jù)蛮寂,再去訪(fǎng)問(wèn)005-008才能讀取完整的int數(shù)據(jù)蔽午,效率較低,所以采用字節(jié)對(duì)齊酬蹋。字節(jié)對(duì)齊的目的是兼容硬件及老,提高效率,利用空間換時(shí)間。
8字節(jié)對(duì)齊開(kāi)辟空間肯定是8的倍數(shù),如果有個(gè)9個(gè)字節(jié)的數(shù)據(jù)需要16個(gè)字節(jié)空間來(lái)存放锨用。
word_align的實(shí)現(xiàn):8的倍數(shù)的二進(jìn)制低三位都是000乘凸,~按位取反
通過(guò)宏定義可以看出,64位下8字節(jié)對(duì)齊嘿歌,32位下4字節(jié)對(duì)齊