理解Tagged Pointer

文章來(lái)源于DC蝸牛的博客《從一個(gè)例子來(lái)看Tagged Pointer特性》

一個(gè)錯(cuò)誤例子

@property (nonatomic, strong) NSString *string;

dispatch_queue_t queue = dispatch_queue_create("memoryBeingFreedCase_4", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 1000000; i++) {

dispatch_async(queue, ^{

self.string = [NSString stringWithFormat:@"The num is %d", i];

});

}

當(dāng)運(yùn)行后, 就會(huì)崩潰在給self.string的一行, 錯(cuò)誤log如下:

malloc: *** error for object 0x600000639480: Non-aligned pointer being freed (2)

錯(cuò)誤原因

來(lái)看下原因是為何? 其實(shí)是因?yàn)閟etter方法中, 對(duì)strong修飾的屬性會(huì)有一個(gè)retain和release的操作。 在并發(fā)多線程中的賦值操作中, 都是對(duì)_string指針進(jìn)行的操作, 可能在_string剛剛被release后進(jìn)行第3行代碼的賦值操作。這時(shí)_string指向的內(nèi)存地址是已經(jīng)被釋放了, 所以造成上面的錯(cuò)誤。

- (void)setString:(NSString *)string {

? ? [string retain];? //1

? ? [_stirng release];//2

? ? _string = string; //3

}

解決方案

1.將并發(fā)執(zhí)行的任務(wù)改為串行執(zhí)行。??

2.將屬性開(kāi)啟atomic原子特性。??

3.利用Tagged Pointer特性。前兩個(gè)方案的具體方法就不絮述了, 只說(shuō)第三個(gè)方案。如果將上面的例子中, 改動(dòng)一行代碼, 重新運(yùn)行。

self.string = [NSString stringWithFormat:@"%d", i];

這時(shí), 你會(huì)發(fā)現(xiàn), 竟然沒(méi)有問(wèn)題了, 這究竟為什么呢, 到底什么這么神奇呢? 下面直接引入本文主題Tagged Pointer。

Tagged Pointer

2013年9月, 蘋(píng)果發(fā)布iPhone5s, 其搭載了蘋(píng)果A7處理器, 是首個(gè)采用64位架構(gòu)的處理器。關(guān)于iPhone系列的處理器指令集可以參閱之前寫(xiě)過(guò)一篇關(guān)于Architectures與指令集架構(gòu)的博客。也是從采用64位處理器后, 為了節(jié)省內(nèi)存和提高執(zhí)行效率,蘋(píng)果提出了Tagged Pointer的概念。對(duì)于64位程序,引入Tagged Pointer后,相關(guān)邏輯能減少一半的內(nèi)存占用,以及3倍的訪問(wèn)速度提升,100倍的創(chuàng)建、銷毀速度提升。

Tagged Pointer 之前

比如, NSInteger類型的變量,它所占用的內(nèi)存是與處理器的位數(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é)。?

所以在沒(méi)有Tagged Pointer對(duì)象之前,從32位機(jī)器遷移到64位機(jī)器中后,雖然邏輯沒(méi)有任何變化,但這種NSNumber、NSDate一類的對(duì)象所占用的內(nèi)存會(huì)翻倍。而且從效率上來(lái)說(shuō),我們需要在堆上為其分配內(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é)。我們可以將一個(gè)對(duì)象的指針拆成兩部分,一部分直接保存數(shù)據(jù),另一部分作為特殊標(biāo)記,表示這是一個(gè)特別的指針,不指向任何一個(gè)地址。所以在這總共8字節(jié)的內(nèi)存中, 把標(biāo)記位除去后, 其他的內(nèi)存大小都可以存儲(chǔ)數(shù)據(jù)。所以,引入了Tagged Pointer對(duì)象之后,其在64位處理器下的內(nèi)存圖變成了以下這樣:?


而且, 如果你所要存儲(chǔ)的數(shù)據(jù)大小超出Tagged Pointer對(duì)象可存儲(chǔ)大小的話, 系統(tǒng)將不會(huì)以Tagged Pointer的方式, 將會(huì)以普通對(duì)象的方式來(lái)保存。所以, 這個(gè)優(yōu)化并不需要人為的干預(yù)。

代碼驗(yàn)證

NSMutableString *string = [NSMutableString stringWithString:@"1"];

for(int i = 0; i < 20; i++){

? ? ? ? NSNumber *number = @([string longLongValue]);

? ? ? ? NSLog(@"%@: %p---%p", [number class], number, &number);

? ? ? ? [string appendString:@"1"];

}

以NSNumber類型舉例, 打印結(jié)果:

__NSCFNumber: 0xb000000000000013---0x7ffee2f43698

__NSCFNumber: 0xb0000000000000b3---0x7ffee2f43698

__NSCFNumber: 0xb0000000000006f3---0x7ffee2f43698

__NSCFNumber: 0xb000000000004573---0x7ffee2f43698

__NSCFNumber: 0xb00000000002b673---0x7ffee2f43698

__NSCFNumber: 0xb0000000001b2073---0x7ffee2f43698

__NSCFNumber: 0xb0000000010f4473---0x7ffee2f43698

__NSCFNumber: 0xb00000000a98ac73---0x7ffee2f43698

__NSCFNumber: 0xb000000069f6bc73---0x7ffee2f43698

__NSCFNumber: 0xb000000423a35c73---0x7ffee2f43698

__NSCFNumber: 0xb000002964619c73---0x7ffee2f43698

__NSCFNumber: 0xb000019debd01c73---0x7ffee2f43698

__NSCFNumber: 0xb000102b36211c73---0x7ffee2f43698

__NSCFNumber: 0xb000a1b01d4b1c73---0x7ffee2f43698

__NSCFNumber: 0xb00650e124ef1c73---0x7ffee2f43698

__NSCFNumber: 0xb03f28cb71571c73---0x7ffee2f43698

__NSCFNumber: 0xb27797f26d671c73---0x7ffee2f43698

__NSCFNumber: 0x6000006297c0---0x7ffee2f43698

__NSCFNumber: 0x6000006297c0---0x7ffee2f43698

__NSCFNumber: 0x6000006297c0---0x7ffee2f43698

通過(guò)結(jié)果分析, 在打印地址中除去最后的數(shù)字最末尾的3以及最開(kāi)頭的0xb, 其它數(shù)字剛好表示了相應(yīng)NSNumber的值??梢?jiàn),蘋(píng)果確實(shí)是將值直接存儲(chǔ)到了指針本身里面。也可能數(shù)字最末尾的3以及最開(kāi)頭的0xb就是蘋(píng)果對(duì)于Tagged Pointer的特殊標(biāo)記。在最后的三行打印結(jié)果中,由于Tagged Pointer無(wú)法將其按上面的壓縮方式來(lái)保存,那么應(yīng)該就會(huì)以普通對(duì)象的方式來(lái)保存, 所以打印的結(jié)果是棧區(qū)和堆區(qū)的內(nèi)存地址。

NSMutableString *string2 = [NSMutableString stringWithString:@"1"];

? ? for( int i = 0; i < 14; i++){

? ? ? ? NSString *strFor = [[string2 mutableCopy] copy];

? ? ? ? NSLog(@"%@: %p---%p", [strFor class], strFor, &strFor);

? ? ? ? [string2 appendString:@"1"];

? ? }

以NSString類型舉例, 打印結(jié)果:

NSTaggedPointerString: 0xa000000000000311---0x7ffee9e64698

NSTaggedPointerString: 0xa000000000031312---0x7ffee9e64698

NSTaggedPointerString: 0xa000000003131313---0x7ffee9e64698

NSTaggedPointerString: 0xa000000313131314---0x7ffee9e64698

NSTaggedPointerString: 0xa000031313131315---0x7ffee9e64698

NSTaggedPointerString: 0xa003131313131316---0x7ffee9e64698

NSTaggedPointerString: 0xa313131313131317---0x7ffee9e64698

NSTaggedPointerString: 0xa0079e79e79e79e8---0x7ffee9e64698

NSTaggedPointerString: 0xa1e79e79e79e79e9---0x7ffee9e64698

NSTaggedPointerString: 0xa03def7bdef7bdea---0x7ffee9e64698

NSTaggedPointerString: 0xa7bdef7bdef7bdeb---0x7ffee9e64698

__NSCFString: 0x60400042f560---0x7ffee9e64698

__NSCFString: 0x600000437060---0x7ffee9e64698

__NSCFString: 0x600000436e80---0x7ffee9e64698

這個(gè)例子中, 前面的部分類型打印出來(lái)都是NSTaggedPointerString, 這就很明顯了。也可能開(kāi)頭的0xa就是蘋(píng)果對(duì)于Tagged Pointer的特殊標(biāo)記。在最后的三行打印結(jié)果中,由于Tagged Pointer無(wú)法將其按上面的壓縮方式來(lái)保存,那么應(yīng)該就會(huì)以普通對(duì)象的方式來(lái)保存, 所有后面的類型也就變?yōu)開(kāi)_NSCFString了。

再看下我在控制臺(tái)進(jìn)行的一些打印:?

這個(gè)打印結(jié)果, 足以說(shuō)明它是一個(gè)特別的指針,且不指向任何一個(gè)地址。所有對(duì)象都有 isa 指針,而Tagged Pointer其實(shí)是沒(méi)有的,因?yàn)樗皇钦嬲膶?duì)象。 所以如果你直接訪問(wèn)Tagged Pointer的isa成員的話,在編譯時(shí)將會(huì)有警告。

特點(diǎn)

1.Tagged Pointer指針的值不再是地址了,而是真正的值。所以,實(shí)際上它不再是一個(gè)對(duì)象了,它只是一個(gè)披著對(duì)象皮的普通變量而已。所以,它的內(nèi)存并不存儲(chǔ)在堆中,也不需要malloc和free。?

2.在內(nèi)存讀取上有著3倍的效率,創(chuàng)建時(shí)比以前快106倍。不但減少了64位機(jī)器下程序的內(nèi)存占用,還提高了運(yùn)行效率。完美地解決了小內(nèi)存對(duì)象在存儲(chǔ)和訪問(wèn)效率上的問(wèn)題。?

3.這是一個(gè)特別的指針,不指向任何一個(gè)地址。?

4.Tagged Pointer沒(méi)有isa指針, 所以其不是真正的對(duì)象。

總結(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)門(mén)agged Pointer并不是真正的對(duì)象,我們?cè)谑褂脮r(shí)需要注意不要直接訪問(wèn)其isa變量。

參考文章:?

深入理解Tagged Pointer?

Let’s Build Tagged Pointers?

Tagged Pointer Strings

?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 前言 在2013年9月,蘋(píng)果推出了iPhone5s,與此同時(shí),iPhone5s配備了首個(gè)采用64位架構(gòu)的A7雙核處...
    woshishui1243閱讀 204評(píng)論 0 0
  • 在調(diào)試程序或者反編譯App時(shí),經(jīng)??梢钥吹?quot;NSTaggedPointerString"這個(gè)東西例如: 打印: 這...
    Mr_Baymax閱讀 10,625評(píng)論 15 48
  • *面試心聲:其實(shí)這些題本人都沒(méi)怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來(lái)就是把...
    Dove_iOS閱讀 27,593評(píng)論 30 472
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,626評(píng)論 1 32
  • 這篇文章寫(xiě)了一周,斷斷續(xù)續(xù),后面加了一些東西,可能有點(diǎn)接不上。 發(fā)完這條微博后,引發(fā)了很多的評(píng)論,于是今天早上快6...
    阿倫影子閱讀 410評(píng)論 0 0

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