天天看點

iOS Tagged Pointer優化(NSString , NSNumber)

我們可以在WWDC2013的《Session 404 Advanced in Objective-C》視訊中,看到蘋果對于Tagged Pointer特點的介紹:

  1. Tagged Pointer專門用來存儲小的對象,例如NSNumber, NSDate, NSString。
  2. Tagged Pointer指針的值不再是位址了,而是真正的值。是以,實際上它不再是一個對象了,它隻是一個披着對象皮的普通變量而已。是以,它的記憶體并不存儲在堆中,也不需要malloc和free。
  3. 在記憶體讀取上有着3倍的效率,建立時比以前快106倍。

既然使用了tagged pointer,那麼就失去了iOS對象的資料結構,但是,系統還是需要有個标志位表明目前的tagged pointer 表示的是什麼類型的對象。這個标志位,也是在最高4位來表示的。

我們将0xa轉換為二進制,得到

1010,其中最高位1xxx表明這是一個tagged pointer,而剩下的3位010,表示了這是一個NSString類型。010轉換為十進制即為2。也就是說,标志位是2的tagger pointer表示這是一個NSString對象。

在字元串長度在9個以内時,iOS其實使用了tagged pointer做了優化的, 直到字元串長度大于9,字元串才真正成為了__NSCFString類型(即對象類型)
  • NSNumber存儲

    位址以0xb開頭,轉換為二進制為1011,首位1表示這是一個tagged pointer,而011轉換為十進制是3,代表對象類型接下來幾位,就是以16進制表示的NSNumber的值,而對于最後一位,應該是一個标志位,具體作用,筆者也不是很清楚

    以上是唐神的部落格,看着覺得很有道理,但是列印完全不一樣,應該是更新了,是以重新摸索一番

字元串繼承鍊

__NSCFConstantString -> __NSCFString -> NSMutableString -> NSString -> NSObject
           
NSString *str = @"abc"; // __NSCFConstantString
NSString *str1 = @"abc"; //__NSCFConstantString
NSString *str2 = [NSString stringWithFormat:@"%@", str]; // NSTaggedPointerString
NSString *str3 = [str copy]; // __NSCFConstantString
NSString *str4 = [str mutableCopy]; // __NSCFString
    
NSLog(@"str(%@<%p>: %p): %@", [str class], &str, str, str);
NSLog(@"str1(%@<%p>: %p): %@", [str1 class], &str1, str1, str1);
NSLog(@"str2(%@<%p>: %p): %@", [str2 class], &str2, str2, str2);
NSLog(@"str3(%@<%p>: %p): %@", [str3 class], &str3, str3, str3);
NSLog(@"str4(%@<%p>: %p): %@", [str4 class], &str4, str4, str4);
           
  1. 在編譯期間,就已經決定 NSString -> __NSCFConstantString。是以同一個字元串常量在堆區隻配置設定一個空間,并且 retainCount 為最大。也就是說不會被釋放掉。
  • NSCFConstantString這種對象一般通過字面值 @"…"、CFSTR("…") 或者 stringWithString: 方法産生。
  • NSTaggedPointerString

    了解這個類型,需要明白什麼是标簽指針,這是蘋果在 64 位環境下對 NSString,NSNumber 等對象做的一些優化。簡單來講可以了解為把指針指向的内容直接放在了指針變量的記憶體位址中,因為在 64 位環境下指針變量的大小達到了 8 位足以容納一些長度較小的内容。于是使用了标簽指針這種方式來優化資料的存儲方式。從他的引用計數可以看出,這貨也是一個釋放不掉的單例常量對象。在運作時根據實際情況建立。

    對于 NSString 對象來講,當非字面值常量的數字,英文字母字元串的長度小于等于 9 的時候會自動成為 NSTaggedPointerString 類型,如果有中文或其他特殊符号(可能是非 ASCII 字元)存在的話則會直接成為 )__NSCFString 類型。

    這種對象被直接存儲在指針的内容中,可以當作一種僞對象。

  • __NSCFString

    和 __NSCFConstantString 不同, __NSCFString 對象是在運作時建立的一種 NSString 子類,他并不是一種字元串常量。是以和其他的對象一樣在被建立時獲得了 1 的引用計數。

    通過 NSString 的 stringWithFormat 等方法建立的 NSString 對象一般都是這種類型。

    這種對象被存儲在堆上。

  • copy與mutableCopy
NSString *str1 = @"sa";
    TLog(str1);
    //str1: __NSCFConstantString -> 0x100001050 : sa  18446744073709551615
    NSString *str2 = @"sa";
    TLog(str2);
    //str2: __NSCFConstantString -> 0x100001050 : sa  18446744073709551615
    NSString *str3 = @"1234567890";
    TLog(str3);
    //str3: __NSCFConstantString -> 0x100001110 : 1234567890  18446744073709551615
    NSString *str4 = [NSString stringWithFormat:@"sa"];
    TLog(str4);
    //str4: NSTaggedPointerString -> 0x617325 : sa  18446744073709551615
    NSString *str5 = [NSString stringWithFormat:@"sa"];
    TLog(str5);
    //str5: NSTaggedPointerString -> 0x617325 : sa  18446744073709551615
    NSString *str6 = [NSString stringWithFormat:@"1強"];
    TLog(str6);
    //str6: NSTaggedPointerString -> 0x1ea1f72bb30ab195 : 123456789  18446744073709551615
    NSString *str7 = [NSString stringWithFormat:@"1234567890"];
    TLog(str7);
    //str7: __NSCFString -> 0x100300800 : 1234567890  1
    
    
    
    
    //copy操作
    NSString *str8 = [str6 copy];
    TLog(str8);
    //__NSCFString -> 0x600001077540 : 1強  3
    
    NSString *str8 = [str5 copy];
    TLog(str8);
    //str8: NSTaggedPointerString -> 0xb1d8c1c416284ebd : sa  9223372036854775807
    
    NSString *str8 = [str5 mutableCopy];
    TLog(str8);
    //str8: __NSCFString -> 0x600001e14150 : sa  1
           

通過輸出結果可以看出,mutableCopy對于任何的String類型 不會改變引用計數,會拷貝内容到堆上,生成一個 __NSCFString 對象,新對象的引用計數為1.

而copy隻有對于NSCFString對象類型引用計數才會加一,其他類型的話就指針還是指向被複制的位址

NSMutableString *mutableString1 = [NSMutableString stringWithFormat:@"fsfsf"];
    TLog(mutableString1);
    //mutableString1: __NSCFString -> 0x600000097450 : fsfsf  2
    
    NSMutableString *mutableString2 = @"12345";
    TLog(mutableString2);
    //mutableString2: __NSCFConstantString -> 0x1060a7230 : 12345  1152921504606846975
    
    NSMutableString *mutableString2 = [NSMutableString stringWithString:@"12345"];
    TLog(mutableString2);
    //mutableString2: __NSCFString -> 0x600000617900 : 12345  1
    
    NSString *str10 = [mutableString1 copy];
    TLog(str10);
    //str10: NSTaggedPointerString -> 0x9bb5473671a403ec : fsfsf  9223372036854775807
    
    NSString *str11 = [mutableString2 mutableCopy];
    TLog(str11);
    //str11: __NSCFString -> 0x6000000971e0 : 12345  1
           

可以看出

  • 建立的可變字元串都是對象類型 __NSCFString(除了直接指派字面常量是__NSCFConstantString除外,這樣寫反正會警告,肯定不這樣寫吧)
  • mutableCopy跟不可變NSString一樣,都是新建立對象,然後引用計數為1
  • copy操作就像是直接取了值,建立了一個NSTaggedPointerString類型,引用計數特别大
  1. 在編譯期間,就已經決定 NSMutableString -> __NSCFString。是以一個可變字元串常量在堆區會配置設定一個空間,并且 retainCount 為 1,也就是說按正常對象的生命周期被釋放。 可變字元串都是對象類型NSCFString,copy操作後才會是tagPointerString

NSNumber 解析

__NSCFNumber -> NSNumber -> NSValue -> NSObject
           

NSNumber也是同一個常量就配置設定一個空間

NSNumber *num4 = @(3.1415927);
           

當NSNumber為浮點型資料或特别大的資料時,不會按tagPointer存儲,是對象類型

總結

在iOS的日常開發中,同樣内容的字元串常量 __NSCFConstantString 全局隻有一份,放在堆區,并且不會被釋放(retainCount值最大)。并且由于有 Tagged Pointer 的存在,盡量避免直接通路對象的 isa 變量。