什么是Tagged Pointer驻售?
在2013年9月嗦明,蘋(píng)果推出了iPhone5s撩荣,與此同時(shí)椭更,iPhone5s配備了首個(gè)采用64位架構(gòu)的A7雙核處理器哪审,為了節(jié)省內(nèi)存和提高執(zhí)行效率,蘋(píng)果提出了Tagged Pointer的概念虑瀑。對(duì)于64位程序湿滓,引入Tagged Pointer后,相關(guān)邏輯能減少一半的內(nèi)存占用舌狗!
那么Tagged Pointer是如何節(jié)省內(nèi)存的呢叽奥?
-
我們先看看原有的對(duì)象為什么會(huì)浪費(fèi)內(nèi)存。假設(shè)我們要存儲(chǔ)一個(gè) NSNumber 對(duì)象把夸,其值是一個(gè)整數(shù)而线。正常情況下,如果這個(gè)整數(shù)只是一個(gè) NSInteger 的普通變量恋日,那么它所占用的內(nèi)存是與 CPU 的位數(shù)有關(guān)膀篮,在 32 位 CPU 下占 4 個(gè)字節(jié),在 64 位 CPU 下是占 8 個(gè)字節(jié)的岂膳。而指針類(lèi)型的大小通常也是與 CPU 位數(shù)相關(guān)誓竿,一個(gè)指針?biāo)加玫膬?nèi)存在 32 位 CPU 下為 4 個(gè)字節(jié),在 64 位 CPU 下也是 8 個(gè)字節(jié)谈截。
所以一個(gè)普通的 iOS 程序筷屡,如果沒(méi)有Tagged Pointer對(duì)象涧偷,從 32 位機(jī)器遷移到 64 位機(jī)器中后,雖然邏輯沒(méi)有任何變化毙死,但這種 NSNumber燎潮、NSDate 一類(lèi)的對(duì)象所占用的內(nèi)存會(huì)翻倍。如下圖所示:
未使用Tagged Pointer內(nèi)存圖.png
從上圖中可以看到內(nèi)存的多余的浪費(fèi)扼倘,以及查找該值的繁瑣邏輯确封!我們?cè)賮?lái)看看效率上的問(wèn)題,為了存儲(chǔ)和訪(fǎng)問(wèn)一個(gè) NSNumber 對(duì)象再菊,我們需要在堆上為其分配內(nèi)存爪喘,另外還要維護(hù)它的引用計(jì)數(shù),管理它的生命期纠拔。這些都給程序增加了額外的邏輯秉剑,造成運(yùn)行效率以及內(nèi)存的損失。
-
為了改進(jìn)上面提到的內(nèi)存占用和效率問(wèn)題稠诲,所以蘋(píng)果提出了Tagged Pointer對(duì)象侦鹏。對(duì)于某些占用內(nèi)存很小的數(shù)據(jù)實(shí)例,不再單獨(dú)開(kāi)辟空間去存儲(chǔ)吕粹,而是將實(shí)際的實(shí)例值存儲(chǔ)在對(duì)象的指針中种柑,同時(shí)對(duì)該指針進(jìn)行標(biāo)記,用于區(qū)分正常的指針指向匹耕!由于NSNumber聚请、NSDate類(lèi)的變量本身的值需要占用的內(nèi)存大小常常不需要8個(gè)字節(jié),拿整數(shù)來(lái)說(shuō)稳其,4個(gè)字節(jié)所能表示的有符號(hào)整數(shù)就可以達(dá)到20多億(注:2^31=2147483648驶赏,另外1位作為符號(hào)位),對(duì)于絕大多數(shù)情況都是可以處理的既鞠。所以蘋(píng)果將一個(gè)對(duì)象的指針拆成兩部分煤傍,一部分直接保存數(shù)據(jù),另一部分作為特殊標(biāo)記嘱蛋,表示這是一個(gè)特別的指針蚯姆,不指向任何一個(gè)地址。所以洒敏,引入了Tagged Pointer對(duì)象之后龄恋,64位CPU下NSNumber的內(nèi)存圖變成了以下這樣:
64位使用Tagged Pointer內(nèi)存圖.png
下面進(jìn)入大家最喜歡的環(huán)節(jié),沒(méi)錯(cuò)凶伙,就是驗(yàn)證郭毕!
1.我先來(lái)看一下Tagged Pointer的初始化過(guò)程
2.在上述的代碼中我們可以看到對(duì)真實(shí)的結(jié)果進(jìn)行了一次encode操作,接下來(lái)我們?cè)賮?lái)看看_objc_encodeTaggedPointer函數(shù)里做了什么操作
將實(shí)際結(jié)果與objc_debug_taggedpointer_obfuscator進(jìn)行異或操作函荣!
-
那么objc_debug_taggedpointer_obfuscator是什么呢显押?我們?cè)賮?lái)看看
objc_debug_taggedpointer_obfuscator@2x.png可以看到在iOS 12系統(tǒng)版本之前objc_debug_taggedpointer_obfuscator是0扳肛,之后是一個(gè)無(wú)符號(hào)長(zhǎng)整型的隨機(jī)數(shù)!那么為什么要改為隨機(jī)數(shù)呢乘碑?是為了一個(gè)簡(jiǎn)便的加密挖息!大家可以試想一下,用一個(gè)值去^一個(gè)0蝉仇,那么結(jié)果是多少旋讹,可以自己嘗試一下V巢稀(可以往密碼加鹽這方面理解)
-
我們所看見(jiàn)的的地址轿衔,實(shí)際上是被編碼過(guò)的,如果想要看到真實(shí)的結(jié)果則需要自己手動(dòng)去解碼睦疫!
objc_decodeTaggedPointer@2x.png
通過(guò)上述代碼可以得知害驹,就是把已編碼后的地址再次進(jìn)行異或得到原來(lái)的值!
我們來(lái)一起看下這特殊地址的廬山真面目蛤育,是不是還有點(diǎn)小激動(dòng) ??
通過(guò)log我們可以看出宛官,0xa以及0xb實(shí)際為特殊標(biāo)識(shí)位,以0xb開(kāi)頭為例瓦糕,將其轉(zhuǎn)換為二進(jìn)制就是1011底洗, 首位1表示這是一個(gè)tagged Pointer,而011轉(zhuǎn)換為十進(jìn)制是3咕娄,參考下圖中tagged Pointer的類(lèi)型枚舉亥揖,可以看出這是一個(gè)NSNumber類(lèi)型!
而末尾的數(shù)字仿佛在代表著類(lèi)型標(biāo)識(shí)符圣勒!經(jīng)過(guò)實(shí)踐可以總結(jié)為:0表示char類(lèi)型费变,1表示short類(lèi)型,2表示整形圣贸,3表示長(zhǎng)整型挚歧,4表示單精度類(lèi)型,5表示雙精度類(lèi)型吁峻!而中間這一部分才是真正的值滑负,可以看到A為char類(lèi)型,返回的是ASCII碼(該值為16進(jìn)制的用含,需要進(jìn)行十進(jìn)制轉(zhuǎn)換)
需要注意的是:TaggedPointerString類(lèi)型的指針與基本類(lèi)型的指針是不一樣的矮慕,末尾的數(shù)字為字符串的長(zhǎng)度!
聲明: 以上只是一些初探耕餐,系統(tǒng)是如何區(qū)分以及劃分的需要大家繼續(xù)去探索凡傅,例如:Tagged Pointer最大可以存儲(chǔ)多大值
總結(jié)
1.TaggedPointer:并不是一個(gè)類(lèi),它是適用于 64位處理器的一個(gè)內(nèi)存優(yōu)化機(jī)制肠缔,專(zhuān)門(mén)用來(lái)存儲(chǔ)小對(duì)象夏跷,當(dāng)存儲(chǔ)不下時(shí)哼转,則轉(zhuǎn)為對(duì)象!例如 NSString槽华、NSNumber 和 NSDate等對(duì)象進(jìn)行優(yōu)化壹蔓。
2.指針不再是地址了,而是經(jīng)過(guò)標(biāo)識(shí)過(guò)的的值猫态。它不再是一個(gè)對(duì)象了佣蓉,只是普通變量而已。所以亲雪,它的內(nèi)存并不存儲(chǔ)在堆中勇凭,也不需要malloc和free。
3.在內(nèi)存讀取上有著3倍的效率义辕,創(chuàng)建時(shí)比以前快106倍虾标。
注意:Tagged Pointer并不是真正的對(duì)象,而是一個(gè)偽對(duì)象灌砖,對(duì)象都有 isa指針璧函,而Tagged Pointer是沒(méi)有的,因?yàn)樗皇钦嬲膶?duì)象基显,不能直接訪(fǎng)問(wèn)Tagged Pointer的isa蘸吓!