NSString 和NSNumber 內(nèi)存地址?

今天小伙伴問(wèn)了一個(gè)問(wèn)題,這兩個(gè)變量地址是否相同骚灸?

NSString *a = @"a";
NSString *b = @"a";

輸出如下:

(__NSCFConstantString *) 1 = 0x0000000109fdf9f8 @"a" (__NSCFConstantString *)2 = 0x0000000109fdf9f8 @"a"

可以看到這兩個(gè)對(duì)象是常量辅甥,所以是存儲(chǔ)在常量區(qū),并且地址是一樣的梳虽。接下來(lái)嘗試其他創(chuàng)建方式:

NSString *c = [NSString stringWithString:a];
NSString *d = [NSString stringWithFormat:a];
NSString *e = [[NSString alloc] initWithFormat:a];
NSString *f = [NSString stringWithFormat:@"asdfhaksfhjkashkhfakskhf"];
NSString *g = [NSString stringWithFormat:@"asdfhaksfhjkashkhfakskhf"];

輸出如下:

(__NSCFConstantString *) 0 = 0x000000010581b9f8 @"a" (NSTaggedPointerString *)1 = 0xa000000000000611 @"a"
(NSTaggedPointerString *) 2 = 0xa000000000000611 @"a" (__NSCFString *)3 = 0x0000604000249ab0 @"asdfhaksfhjkashkhfakskhf"
(__NSCFString *) $4 = 0x0000604000249ab0 @"asdfhaksfhjkashkhfakskhf"

可以看到創(chuàng)建方式不同地址也是不同的,initWithString和@“”創(chuàng)建的字符串是一樣的都是常量灾茁,而initWithFormat和stringWthFormat創(chuàng)建的字符串為NSTaggedPointerString形式窜觉,也就是所謂的Tagged Pointer對(duì)象,在字符串特別長(zhǎng)時(shí)生成的是__NSCFString對(duì)象北专,
查閱資料之后得知NSNumber的存儲(chǔ)也有Tagged Pointer禀挫。
蘋(píng)果創(chuàng)建Tagged Pointer的背景如下:

在2013年9月,蘋(píng)果推出了iPhone5s逗余,與此同時(shí)特咆,iPhone5s配備了首 個(gè)采用64位架構(gòu)的A7雙核處理器,為了節(jié)省內(nèi)存和提高執(zhí)行效率,蘋(píng)果提出了Tagged Pointer的概念腻格。先看看原有的對(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ì)翻倍。

為了存儲(chǔ)和訪問(wèn)一個(gè)NSNumber對(duì)象鲁猩,我們需要在堆上為其分配內(nèi)存坎怪,另外還要維護(hù)它的引用計(jì)數(shù),管理它的生命期廓握。這些都給程序增加了額外的邏輯搅窿,造成運(yùn)行效率上的損失。

為了改進(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多億开仰。所以我們可以將一個(gè)對(duì)象的指針拆成兩部分拟枚,一部分直接保存數(shù)據(jù),另一部分作為特殊標(biāo)記众弓,表示這是一個(gè)特別的指針恩溅,不指向任何一個(gè)地址。

于是谓娃,簡(jiǎn)單來(lái)講可以理解為把指針指向的內(nèi)容直接放在了指針變量的內(nèi)存地址中脚乡,因?yàn)樵?64 位環(huán)境下指針變量的大小達(dá)到了 8 位足以容納一些長(zhǎng)度較小的內(nèi)容。于是使用了標(biāo)簽指針這種方式來(lái)優(yōu)化數(shù)據(jù)的存儲(chǔ)方式。從引用計(jì)數(shù)可以看出奶稠,這個(gè)是一個(gè)釋放不掉的單例常量對(duì)象俯艰。在運(yùn)行時(shí)根據(jù)實(shí)際情況創(chuàng)建。
Tagged Pointer 示例

首先先看NSNumber數(shù)值對(duì)象

muStr2 = [NSMutableString stringWithString:@"1"];
for(int i=0; i<20; i+=1){
NSNumber *number = @([muStr2 longLongValue]);
NSLog(@"%@, %p", [number class], number);
[muStr2 appendString:@"1"];
}
// 輸出結(jié)果
__NSCFNumber, 0xb000000000000013
__NSCFNumber, 0xb0000000000000b3
__NSCFNumber, 0xb0000000000006f3
__NSCFNumber, 0xb000000000004573
__NSCFNumber, 0xb00000000002b673
__NSCFNumber, 0xb0000000001b2073
__NSCFNumber, 0xb0000000010f4473
__NSCFNumber, 0xb00000000a98ac73
__NSCFNumber, 0xb000000069f6bc73
__NSCFNumber, 0xb000000423a35c73
__NSCFNumber, 0xb000002964619c73
__NSCFNumber, 0xb000019debd01c73
__NSCFNumber, 0xb000102b36211c73
__NSCFNumber, 0xb000a1b01d4b1c73
__NSCFNumber, 0xb00650e124ef1c73
__NSCFNumber, 0xb03f28cb71571c73
__NSCFNumber, 0xb27797f26d671c73
__NSCFNumber, 0x60000003d540
__NSCFNumber, 0x61000003cb40
__NSCFNumber, 0x61800003c760

數(shù)值是1锌订、11竹握、111、1111…..這樣遞增辆飘,可以從輸出指針的地址看出最低4位一直為3啦辐,這個(gè)用于標(biāo)記是long(float則為4,Int為2蜈项,double為5)芹关,而最高4位的“b”表示是NSNumber類型;其余56位則用來(lái)存儲(chǔ)數(shù)值本身內(nèi)容紧卒。當(dāng)存儲(chǔ)用的數(shù)值超過(guò)56位存儲(chǔ)上限的時(shí)候侥衬,那么NSNumber才會(huì)用真正的64位內(nèi)存地址存儲(chǔ)數(shù)值,然后用指針指向該內(nèi)存地址常侦。(如果數(shù)值長(zhǎng)度超過(guò)64位浇冰,那么就crash)。

以上的NSString類型也是和NSNumber一個(gè)道理聋亡,最低位表示字符串的長(zhǎng)度肘习,而其余的56位也是用來(lái)存儲(chǔ)數(shù)組,這里需要注意的是坡倔,當(dāng)字符串內(nèi)存長(zhǎng)度超過(guò)了56位的時(shí)候漂佩,Tagged Pointer并沒(méi)有立即用指針轉(zhuǎn)向,而是用了一種算法編碼罪塔,把字符串長(zhǎng)度進(jìn)行壓縮存儲(chǔ)投蝉,當(dāng)這個(gè)算法壓縮的數(shù)據(jù)長(zhǎng)度超過(guò)56位了才使用指針指向。
特點(diǎn)

我們也可以在WWDC2013的《Session 404 Advanced in Objective-C》視頻中征堪,看到蘋(píng)果對(duì)于Tagged Pointer特點(diǎn)的介紹:
1.Tagged Pointer專門(mén)用來(lái)存儲(chǔ)小的對(duì)象瘩缆,例如NSNumber和NSDate,
2.Tagged Pointer指針的值不再是地址了,而是真正的值佃蚜。所以庸娱,實(shí)際上它不再是一個(gè)對(duì)象了,它只是一個(gè)披著對(duì)象皮的普通變量而已谐算。所以熟尉,它的內(nèi)存并不存儲(chǔ)在堆中,也不需要malloc和free洲脂。
3.在內(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)題棚放。

因?yàn)門(mén)agged Pointed不是一個(gè)真正的對(duì)象枚粘,所以其沒(méi)有isa。不過(guò)只要避免在代碼中直接訪問(wèn)對(duì)象的isa變量飘蚯,就沒(méi)問(wèn)題馍迄。具體如Tagged Pointer 怎么訪問(wèn)類方法列表,之后再詳細(xì)看下局骤,也許是根據(jù)最夠?yàn)榈念愋蜆?biāo)記攀圈,然后調(diào)用對(duì)應(yīng)的class方法列表。
總結(jié)

@“” 和 initWithString:方法生成的字符串分配在常量區(qū)峦甩,系統(tǒng)自動(dòng)管理內(nèi)存赘来;
initWithFormat:和 stringWithFormat: 方法生成的字符串分配在堆區(qū);
當(dāng)數(shù)據(jù)內(nèi)容超出Tagged Pointed能存儲(chǔ)的內(nèi)容時(shí)凯傲,就會(huì)像正常的創(chuàng)建對(duì)象一樣創(chuàng)建指針和數(shù)據(jù)內(nèi)容犬辰。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市冰单,隨后出現(xiàn)的幾起案子幌缝,更是在濱河造成了極大的恐慌,老刑警劉巖诫欠,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涵卵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡荒叼,警方通過(guò)查閱死者的電腦和手機(jī)轿偎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)被廓,“玉大人坏晦,你說(shuō)我怎么就攤上這事〖蕹耍” “怎么了英遭?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)亦渗。 經(jīng)常有香客問(wèn)我,道長(zhǎng)汁尺,這世上最難降的妖魔是什么法精? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上搂蜓,老公的妹妹穿的比我還像新娘狼荞。我一直安慰自己,他們只是感情好帮碰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布相味。 她就那樣靜靜地躺著,像睡著了一般殉挽。 火紅的嫁衣襯著肌膚如雪丰涉。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,816評(píng)論 1 290
  • 那天斯碌,我揣著相機(jī)與錄音一死,去河邊找鬼。 笑死傻唾,一個(gè)胖子當(dāng)著我的面吹牛投慈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冠骄,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼伪煤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了凛辣?” 一聲冷哼從身側(cè)響起抱既,我...
    開(kāi)封第一講書(shū)人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蟀给,沒(méi)想到半個(gè)月后蝙砌,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡跋理,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年择克,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片前普。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肚邢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拭卿,到底是詐尸還是另有隱情骡湖,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布峻厚,位于F島的核電站响蕴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏惠桃。R本人自食惡果不足惜浦夷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一辖试、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧劈狐,春花似錦罐孝、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至续膳,卻和暖如春改艇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背姑宽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工遣耍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炮车。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓舵变,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親瘦穆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纪隙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容