iOS NSString和NSNumber內(nèi)存分配

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

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

輸出如下:

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

可以看到這兩個(gè)對(duì)象是常量,所以是存儲(chǔ)在常量區(qū),并且地址是一樣的绍移。接下來嘗試其他創(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瞬测。

蘋果創(chuàng)建Tagged Pointer的背景如下:

在2013年9月,蘋果推出了iPhone5s纠炮,與此同時(shí)月趟,iPhone5s配備了首 個(gè)采用64位架構(gòu)的A7雙核處理器,為了節(jié)省內(nèi)存和提高執(zhí)行效率抗碰,蘋果提出了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程序是复,如果沒有Tagged Pointer對(duì)象,從32位機(jī)器遷移到64位機(jī)器中后竖螃,雖然邏輯沒有任何變化淑廊,但這種NSNumber、NSDate一類的對(duì)象所占用的內(nèi)存會(huì)翻倍特咆。

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

為了改進(jìn)上面提到的內(nèi)存占用和效率問題青抛,蘋果提出了Tagged Pointer對(duì)象。由于NSNumber酬核、NSDate一類的變量本身的值需要占用的內(nèi)存大小常常不需要8個(gè)字節(jié)脂凶,拿整數(shù)來說,4個(gè)字節(jié)所能表示的有符號(hào)整數(shù)就可以達(dá)到20多億愁茁。所以我們可以將一個(gè)對(duì)象的指針拆成兩部分蚕钦,一部分直接保存數(shù)據(jù),另一部分作為特殊標(biāo)記鹅很,表示這是一個(gè)特別的指針嘶居,不指向任何一個(gè)地址。

于是促煮,簡(jiǎn)單來講可以理解為把指針指向的內(nèi)容直接放在了指針變量的內(nèi)存地址中邮屁,因?yàn)樵?64 位環(huán)境下指針變量的大小達(dá)到了 8 位足以容納一些長(zhǎng)度較小的內(nèi)容。于是使用了標(biāo)簽指針這種方式來優(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位則用來存儲(chǔ)數(shù)值本身內(nèi)容薪铜。當(dāng)存儲(chǔ)用的數(shù)值超過56位存儲(chǔ)上限的時(shí)候众弓,那么NSNumber才會(huì)用真正的64位內(nèi)存地址存儲(chǔ)數(shù)值,然后用指針指向該內(nèi)存地址隔箍。(如果數(shù)值長(zhǎng)度超過64位田轧,那么就crash)。

以上的NSString類型也是和NSNumber一個(gè)道理鞍恢,最低位表示字符串的長(zhǎng)度傻粘,而其余的56位也是用來存儲(chǔ)數(shù)組,這里需要注意的是帮掉,當(dāng)字符串內(nèi)存長(zhǎng)度超過了56位的時(shí)候弦悉,Tagged Pointer并沒有立即用指針轉(zhuǎn)向,而是用了一種算法編碼蟆炊,把字符串長(zhǎng)度進(jìn)行壓縮存儲(chǔ)稽莉,當(dāng)這個(gè)算法壓縮的數(shù)據(jù)長(zhǎng)度超過56位了才使用指針指向。

特點(diǎn)

我們也可以在WWDC2013的《Session 404 Advanced in Objective-C》視頻中涩搓,看到蘋果對(duì)于Tagged Pointer特點(diǎn)的介紹:
1.Tagged Pointer專門用來存儲(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倍。
由此可見肘习,蘋果引入Tagged Pointer际乘,不但減少了64位機(jī)器下程序的內(nèi)存占用,還提高了運(yùn)行效率漂佩。完美地解決了小內(nèi)存對(duì)象在存儲(chǔ)和訪問效率上的問題脖含。

因?yàn)門agged Pointed不是一個(gè)真正的對(duì)象,所以其沒有isa仅仆。不過只要避免在代碼中直接訪問對(duì)象的isa變量器赞,就沒問題垢袱。具體如Tagged Pointer 怎么訪問類方法列表墓拜,之后再詳細(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)容。

參考

iOS Tagged Pointer
i深入理解Tagged Pointer

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末臣樱,一起剝皮案震驚了整個(gè)濱河市靶擦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雇毫,老刑警劉巖玄捕,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異棚放,居然都是意外死亡枚粘,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門飘蚯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來馍迄,“玉大人,你說我怎么就攤上這事局骤∨嗜Γ” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵峦甩,是天一觀的道長(zhǎng)量承。 經(jīng)常有香客問我,道長(zhǎng)穴店,這世上最難降的妖魔是什么撕捍? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮泣洞,結(jié)果婚禮上忧风,老公的妹妹穿的比我還像新娘。我一直安慰自己球凰,他們只是感情好狮腿,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著呕诉,像睡著了一般缘厢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上甩挫,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天贴硫,我揣著相機(jī)與錄音,去河邊找鬼。 笑死英遭,一個(gè)胖子當(dāng)著我的面吹牛间护,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挖诸,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼汁尺,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了多律?” 一聲冷哼從身側(cè)響起痴突,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎狼荞,沒想到半個(gè)月后苞也,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡粘秆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年如迟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片攻走。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡殷勘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昔搂,到底是詐尸還是另有隱情玲销,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布摘符,位于F島的核電站贤斜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏逛裤。R本人自食惡果不足惜瘩绒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望带族。 院中可真熱鬧锁荔,春花似錦、人聲如沸蝙砌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽择克。三九已至恬总,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肚邢,已是汗流浹背壹堰。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缀旁。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓记劈,卻偏偏與公主長(zhǎng)得像勺鸦,于是被迫代替她去往敵國和親并巍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345