(2)NSNumber及Tagged Pointer

根據(jù)上一篇文章的總結(jié),我們很容易發(fā)現(xiàn)

@interface Student : NSObject
{
    @public
    int _age;
    int _no;
}

一個(gè)Student對(duì)象在64位架構(gòu)下占了16個(gè)字節(jié),其中isa占8個(gè)字節(jié),兩個(gè)int變量分別占了4個(gè)字節(jié),但是這種方式適合所有OC對(duì)象嗎??哈哈,并不是。。。

今天早上有朋友問NSNumber為啥占用8個(gè)字節(jié)(64bit),請(qǐng)看NSNumber頭文件,發(fā)現(xiàn)如下代碼:

@property (readonly) char charValue;
@property (readonly) unsigned char unsignedCharValue;
@property (readonly) short shortValue;
@property (readonly) unsigned short unsignedShortValue;
@property (readonly) int intValue;
@property (readonly) unsigned int unsignedIntValue;
@property (readonly) long longValue;
@property (readonly) unsigned long unsignedLongValue;
@property (readonly) long long longLongValue;
@property (readonly) unsigned long long unsignedLongLongValue;
@property (readonly) float floatValue;
@property (readonly) double doubleValue;
@property (readonly) BOOL boolValue;
@property (readonly) NSInteger integerValue API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@property (readonly) NSUInteger unsignedIntegerValue API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

NSNumber對(duì)象里面有很多只讀屬性(其實(shí)并不會(huì)生成對(duì)應(yīng)的成員變量),為啥只占8個(gè)字節(jié)呢??我們先做幾個(gè)試驗(yàn):

NSNumber *number1 = [NSNumber numberWithInt:9];
NSNumber *number2 = [NSNumber numberWithInt:9];
NSNumber *number3 = [NSNumber numberWithInt:5];
NSNumber *number4 = [NSNumber numberWithInt:6];
NSLog(@"\n%p\n%p\n%p\n%p", number1, number2,number3, number4);

下面是打印結(jié)果:

0xb000000000000093
0xb000000000000093
0xb000000000000052
0xb000000000000062

我們發(fā)現(xiàn):

  • number1和number2的對(duì)象地址竟然是一樣的
  • 這幾個(gè)地址除了0xb和后面的3、2,其它的數(shù)剛好對(duì)應(yīng)其NSNumber的值

所以蘋果確實(shí)是將值直接存在了指針本身當(dāng)中了

Google上發(fā)現(xiàn)一張NSNumber的內(nèi)存圖,很形象:

NSNumbe

這就很有意思了,我嘗試著打印下他們的ISA指針,發(fā)現(xiàn)報(bào)如下錯(cuò)誤:

image

這是為什么呢,通過查找一些資料發(fā)現(xiàn),唐巧在很早前的一篇文章中提到Tagged Pointer:一下是摘錄:

在WWDC2013的《Session 404 Advanced in Objective-C》視頻中,蘋果介紹了Tagged Pointer。Tagged Pointer的存在主要是為了節(jié)省內(nèi)存。我們知道,對(duì)象的指針大小一般是與機(jī)器字長有關(guān),在32位系統(tǒng)中,一個(gè)指針的大小是32位(4字節(jié)),而在64位系統(tǒng)中,一個(gè)指針的大小將是64位(8字節(jié))。

在64位系統(tǒng)中,如果我們真正使用一個(gè)指針來存儲(chǔ)NSNumber實(shí)例,那么我們首先需要一個(gè)8字節(jié)的指針,另外需要一塊內(nèi)存存儲(chǔ)NSNumber實(shí)例,這通常又是8字節(jié)。這樣的內(nèi)存開銷是比較大的。蘋果對(duì)于NSNumber和NSDate對(duì)象,改成了用Tagged Pointer來存儲(chǔ),簡單來說,Tagged Pointer是一個(gè)假的指針,它的值不再是另一個(gè)地址,而就是對(duì)應(yīng)變量的值。

Tagged Pointer主要有以下3個(gè)特點(diǎn):

Tagged Pointer專門用來存儲(chǔ)小的對(duì)象,例如NSNumber和NSDate
Tagged Pointer指針的值不再是地址了,而是真正的值。所以,實(shí)際上它不再是一個(gè)對(duì)象了,它只是一個(gè)披著對(duì)象皮的普通變量而已!所以,它的內(nèi)存并不存儲(chǔ)在堆中,也不需要malloc和free。
在內(nèi)存讀取上有著3倍的效率(以前是尋址->發(fā)消息->獲取值,現(xiàn)在直接獲取值),創(chuàng)建時(shí)比以前快106倍。

相關(guān)英文文檔截圖如下:

image

看了上面的文章,終于恍然大悟了,大神不愧是大神,這篇文章是14年初寫的。。。

所以我們得出如下結(jié)論:

  • Tagged Pointer并不是真正的對(duì)象,而是一個(gè)偽對(duì)象

因?yàn)?code>Tagged Pointer不是一個(gè)真正的對(duì)象,所以當(dāng)你訪問它的ISA的時(shí)候自然就會(huì)報(bào)上面的錯(cuò)誤了。

如果一個(gè)數(shù)超過了Tagged Pointer所能表示的范圍,又會(huì)怎么處理呢?同樣做個(gè)試驗(yàn):

NSNumber *bigNumber = @(0xEFFFFFFFFFFFFFFF);
NSLog(@"%p", bigNumber);

打印結(jié)果:

0x6000002310c0

我們發(fā)現(xiàn)bigNumber更像一個(gè)普通的地址,跟他本身的值并沒有什么關(guān)系,我們可以打印一下他的ISA,發(fā)現(xiàn)是可以打印的:

image

所以可以得出如下結(jié)論:

  • 當(dāng)8字節(jié)可以承載用于表示的數(shù)值時(shí),系統(tǒng)就會(huì)以Tagged Pointer的方式生成指針,如果8字節(jié)承載不了時(shí),則又用以前的方式來生成普通的指針。

References:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容