算是知道為什么nsstring不能用== 判斷了. 因?yàn)?==判斷的是指針
前言
在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)存占用贯城,以及3倍的訪問(wèn)速度提升,100倍的創(chuàng)建鲫骗、銷毀速度提升悲雳。本文從Tagged Pointer
試圖解決的問(wèn)題入手,帶領(lǐng)讀者理解Tagged Pointer
的實(shí)現(xiàn)細(xì)節(jié)和優(yōu)勢(shì)合瓢,最后指出了使用時(shí)的注意事項(xiàng)。
問(wèn)題
我們先看看原有的對(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é)的霍狰。而指針類型的大小通常也是與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一類的對(duì)象所占用的內(nèi)存會(huì)翻倍郎嫁。如下圖所示:
我們?cè)賮?lái)看看效率上的問(wèn)題,為了存儲(chǔ)和訪問(wèn)一個(gè)NSNumber對(duì)象尚辑,我們需要在堆上為其分配內(nèi)存盔腔,另外還要維護(hù)它的引用計(jì)數(shù)月褥,管理它的生命期瓢喉。這些都給程序增加了額外的邏輯,造成運(yùn)行效率上的損失栓票。
Tagged Pointer
為了改進(jìn)上面提到的內(nèi)存占用和效率問(wèn)題,蘋(píng)果提出了Tagged Pointer
對(duì)象佛猛。由于NSNumber坠狡、NSDate一類的變量本身的值需要占用的內(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ù)情況都是可以處理的触幼。
所以我們可以將一個(gè)對(duì)象的指針拆成兩部分究飞,一部分直接保存數(shù)據(jù),另一部分作為特殊標(biāo)記亿傅,表示這是一個(gè)特別的指針葵擎,不指向任何一個(gè)地址。所以酬滤,引入了Tagged Pointer
對(duì)象之后,64位CPU下NSNumber的內(nèi)存圖變成了以下這樣:
對(duì)此氯檐,我們也可以用 Xcode做實(shí)驗(yàn)來(lái)驗(yàn)證体捏。我們的實(shí)驗(yàn)代碼如下:
{
@autoreleasepool {
NSNumber *number1 = @1;
NSNumber *number2 = @2;
NSNumber *number3 = @3;
NSNumber *numberFFFF = @(0xFFFF);
NSLog(@"number1 pointer is %p", number1);
NSLog(@"number2 pointer is %p", number2);
NSLog(@"number3 pointer is %p", number3);
NSLog(@"numberffff pointer is %p", numberFFFF);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
在該代碼中,我們將幾個(gè)Number類型的指針的值直接輸出耗拓。需要注意的是,我們需要將模擬器切換成 64位的CPU來(lái)測(cè)試樟插,如下圖所示:
[圖片上傳中...(image-af9129-1518072128646-2)]
運(yùn)行之后竿刁,我們得到的結(jié)果如下,可以看到食拜,除去最后的數(shù)字最末尾的2以及最開(kāi)頭的0xb,其它數(shù)字剛好表示了相應(yīng)NSNumber的值流强。
number2 pointer is 0xb000000000000022
number3 pointer is 0xb000000000000032
numberFFFF pointer is 0xb0000000000ffff2
可見(jiàn)呻待,蘋(píng)果確實(shí)是將值直接存儲(chǔ)到了指針本身里面。我們還可以猜測(cè)奏篙,數(shù)字最末尾的2以及最開(kāi)頭的0xb是否就是蘋(píng)果對(duì)于Tagged Pointer
的特殊標(biāo)記呢迫淹?我們嘗試放一個(gè)8字節(jié)的長(zhǎng)的整數(shù)到NSNumber
實(shí)例中,對(duì)于這樣的實(shí)例敛熬,由于Tagged Pointer
無(wú)法將其按上面的壓縮方式來(lái)保存应民,那么應(yīng)該就會(huì)以普通對(duì)象的方式來(lái)保存,我們的實(shí)驗(yàn)代碼如下:
NSLog(@"bigNumber pointer is %p", bigNumber);
運(yùn)行之后瑞妇,結(jié)果如下,驗(yàn)證了我們的猜測(cè)改备,bigNumber
的地址更像是一個(gè)普通的指針地址蔓倍,和它本身的值看不出任何關(guān)系:
可見(jiàn)盐捷,當(dāng)8字節(jié)可以承載用于表示的數(shù)值時(shí)默勾,系統(tǒng)就會(huì)以Tagged Pointer
的方式生成指針,如果8字節(jié)承載不了時(shí)滞诺,則又用以前的方式來(lái)生成普通的指針环疼。關(guān)于以上關(guān)于Tag Pointer
的存儲(chǔ)細(xì)節(jié),我們也可以在這里找到相應(yīng)的討論炫隶,但是其中關(guān)于Tagged Pointer
的實(shí)現(xiàn)細(xì)節(jié)與我們的實(shí)驗(yàn)并不相符,筆者認(rèn)為可能是蘋(píng)果更改了具體的實(shí)現(xiàn)細(xì)節(jié)煞檩,并且這并不影響Tagged Pointer
我們討論Tagged Pointer
本身的優(yōu)點(diǎn)栅贴。
特點(diǎn)
我們也可以在WWDC2013的《Session 404 Advanced in Objective-C》視頻中,看到蘋(píng)果對(duì)于Tagged Pointer
特點(diǎn)的介紹:
-
Tagged Pointer
專門(mén)用來(lái)存儲(chǔ)小的對(duì)象,例如NSNumber
和NSDate
-
Tagged Pointer
指針的值不再是地址了厨剪,而是真正的值友存。所以,實(shí)際上它不再是一個(gè)對(duì)象了屡立,它只是一個(gè)披著對(duì)象皮的普通變量而已。所以勇皇,它的內(nèi)存并不存儲(chǔ)在堆中焚刺,也不需要malloc和free。 - 在內(nèi)存讀取上有著3倍的效率乳愉,創(chuàng)建時(shí)比以前快106倍。
由此可見(jiàn)捕虽,蘋(píng)果引入Tagged Pointer
,不但減少了64位機(jī)器下程序的內(nèi)存占用房揭,還提高了運(yùn)行效率挖滤。完美地解決了小內(nèi)存對(duì)象在存儲(chǔ)和訪問(wèn)效率上的問(wèn)題。
isa指針
Tagged Pointer
的引入也帶來(lái)了問(wèn)題斩松,即Tagged Pointer
因?yàn)椴⒉皇钦嬲膶?duì)象惧盹,而是一個(gè)偽對(duì)象,所以你如果完全把它當(dāng)成對(duì)象來(lái)使钧椰,可能會(huì)讓它露馬腳。比如我在《Objective-C對(duì)象模型及應(yīng)用》一文中就寫(xiě)道瓶埋,所有對(duì)象都有 isa
指針诊沪,而Tagged Pointer
其實(shí)是沒(méi)有的,因?yàn)樗皇钦嬲膶?duì)象晕粪。 因?yàn)椴皇钦嬲膶?duì)象渐裸,所以如果你直接訪問(wèn)Tagged Pointer
的isa
成員的話,在編譯時(shí)將會(huì)有如下警告:
[圖片上傳中...(image-2d8af9-1518072128645-1)]
對(duì)于上面的寫(xiě)法昏鹃,應(yīng)該換成相應(yīng)的方法調(diào)用,如 isKindOfClass
和 object_getClass
怠褐。只要避免在代碼中直接訪問(wèn)對(duì)象的isa變量您宪,即可避免這個(gè)問(wèn)題奠涌。
總結(jié)
蘋(píng)果將Tagged Pointer
引入磷杏,給64位系統(tǒng)帶來(lái)了內(nèi)存的節(jié)省和運(yùn)行效率的提高极祸。Tagged Pointer
通過(guò)在其最后一個(gè)bit位設(shè)置一個(gè)特殊標(biāo)記,用于將數(shù)據(jù)直接保存在指針本身中遥金。因?yàn)?code>Tagged Pointer并不是真正的對(duì)象,我們?cè)谑褂脮r(shí)需要注意不要直接訪問(wèn)其isa變量选泻。
http://www.infoq.com/cn/articles/deep-understanding-of-tagged-pointer/