iOS Tagged Pointer

這篇文章是參考很多資料才寫出來的,有部分內(nèi)容這幾位寫的都很詳細(xì)到位,所以就直接拷貝了,這里向這幾位作者學(xué)習(xí):
深入理解Tagged Pointer
采用Tagged Pointer的字符串
字面量(Literal)

關(guān)于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)。
因?yàn)門agged Pointed不是一個(gè)真正的對(duì)象,所以其沒有isa。不過只要避免在代碼中直接訪問對(duì)象的isa變量,就沒問題。具體如Tagged Pointer 怎么訪問類方法列表,之后再詳細(xì)看下,也許是根據(jù)最夠?yàn)榈念愋蜆?biāo)記,然后調(diào)用對(duì)應(yīng)的class方法列表。

再來看看Tagged Pointer String

NSString *str = @"A";
NSString *str2 = [[str mutableCopy] copy];
NSLog(@"str:%p %@", str, str.class);
NSLog(@"str2:%p %@", str2, str2.class);
// 輸出結(jié)果
str:0x1068a2148 __NSCFConstantString
str2:0xa000000000000411 NSTaggedPointerString

String的TaggedPointer大致和Number一樣,最高位表示類型,最低位表示字符串長(zhǎng)度,然后字符串內(nèi)容轉(zhuǎn)為為ASCII碼存儲(chǔ)(上面的例子A的ASCII為65,轉(zhuǎn)換為16進(jìn)制是41,而1的ASCII碼是49,轉(zhuǎn)換為十六進(jìn)制則是31)

NSMutableString *muStr2 = [NSMutableString stringWithString:@"1"];
for(int i=0; i<14; i+=1){       
    NSString *strFor = [[muStr2 mutableCopy] copy];
    NSLog(@"%@, %p", [strFor class], strFor);
    [muStr2 appendString:@"1"];
}
// 輸出結(jié)果
NSTaggedPointerString, 0xa000000000000311
NSTaggedPointerString, 0xa000000000031312
NSTaggedPointerString, 0xa000000003131313
NSTaggedPointerString, 0xa000000313131314
NSTaggedPointerString, 0xa000031313131315
NSTaggedPointerString, 0xa003131313131316
NSTaggedPointerString, 0xa313131313131317
NSTaggedPointerString, 0xa0079e79e79e79e8
NSTaggedPointerString, 0xa1e79e79e79e79e9
NSTaggedPointerString, 0xa03def7bdef7bdea
NSTaggedPointerString, 0xa7bdef7bdef7bdeb
__NSCFString, 0x60000003e7c0
__NSCFString, 0x61800003e5a0
__NSCFString, 0x60800003e2c0

從上面的指針輸出可以看出,最低位表示字符串的長(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)擊查看具體算法編碼,在此謝謝評(píng)論區(qū)里 _黑蘋果 的說明啦)

關(guān)于String常量

當(dāng)String的內(nèi)容有中文或者特殊字符(非 ASCII 字符)時(shí),那么就只能存儲(chǔ)為String指針
但是字面型字符串常量卻從不存儲(chǔ)為Tagged Pointer。字符串常量必須在不同的操作系統(tǒng)版本下保持二進(jìn)制兼容,而Tagged Pointer的內(nèi)部細(xì)節(jié)是沒有保證的。其能使用的前提是Tagged Pointer在運(yùn)行時(shí)總是由Apple的代碼生成(運(yùn)行時(shí)才能確定),如果編譯器把它們嵌入二進(jìn)制里(編譯),那么前提就被打破了(字符串常量就是這樣)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,767評(píng)論 0 9
  • 在調(diào)試程序或者反編譯App時(shí),經(jīng)常可以看到"NSTaggedPointerString"這個(gè)東西例如: 打印: 這...
    Mr_Baymax閱讀 10,467評(píng)論 15 48
  • *面試心聲:其實(shí)這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,205評(píng)論 30 471
  • iOS面試小貼士 ———————————————回答好下面的足夠了------------------------...
    不言不愛閱讀 2,002評(píng)論 0 7
  • 前言 我第一次開始重視Objective-C Runtime是從2014年11月1日,@唐巧老師在微博上發(fā)的一條微...
    一縷殤流化隱半邊冰霜閱讀 47,139評(píng)論 178 616