再探NSString NSString應該是oc開發中最常用的一個數據類型了,這次對該類型再進行一次全方位的探索與總結。 NSString本質上屬於OC類對象,繼承於NSObject,遵守NSCopying, NSMutableCopying, NSSecureCoding協議。 NSMutable ...
再探NSString
NSString應該是oc開發中最常用的一個數據類型了,這次對該類型再進行一次全方位的探索與總結。
NSString本質上屬於OC類對象,繼承於NSObject,遵守NSCopying, NSMutableCopying, NSSecureCoding協議。
NSMutableString與之類似,唯一不同的是它繼承於NSString。
通過語法糖創建NSString
大部分的開發中,我們都會使用@+雙引號的方式創建NSString對象。如下:
NSString* str=@"i am a single str";
這種創建方式創建出來的類型是什麼呢?它存儲的空間地址又在哪裡呢?不妨列印一下:
NSLog(@"%@:%p",[str class],str);
//列印如下:
//__NSCFConstantString:0x100002058
為什麼明明是NSString類型的在這裡會變成__NSCFConstantString類型,這是因為NSString其實是個類族(大部分容器類也是如此),它的初始化方法返回的實例對象其實是隱藏在類族中的公共介面後面的某一內部類型。
類族(class cluster):屬於一種設計模式,用以隱藏“抽象基類”背後的實現細節。oc的系統框架中普遍使用此模式。
其實__NSCFConstantString類型是一種字元串的常量類型,當切換成MRC模式下使用retainCount會發現它的引用計數會非常大,通常為:2^64-1,這樣設置的原因就是無論對其進行多次release都能夠保證對象不會被釋放,所以可以直接將其看作為一個單例。對於單例對象,那麼只要有相同的內容,他們的記憶體地址也就相同。如下所示:
NSString* str0=@"i am not a single str";
NSString* str1=@"i am not a single str";
NSLog(@"%@:%p",[str0 class],str0);
NSLog(@"%@:%p",[str1 class],str1);
//列印如下:
//__NSCFConstantString:0x100002050
//__NSCFConstantString:0x100002050
這些字元串對象都存儲於字元串常量區。
OC有五大記憶體管理區域,地址由小到大分別為:代碼段、數據段、BSS段、堆、棧。
__NSCFConstantString類型的字元串常量就存儲於數據段當中的常量區。
通過stringWithFormat創建NSString
通常在開發中,需要將得到的臨時變數或者類對象的字元串進行拼接時會用到這個類方法class method。
那麼這種創建方式創建出來的類型是什麼呢?它存儲的空間地址又在哪裡呢?不妨再次列印一下:
NSString* str=[NSString stringWithFormat:@"hi"];
NSLog(@"%@:%p",[str class],str);
//列印如下:
NSTaggedPointerString:0x723673ffe0bc6c91
說到NSTaggedPointerString這個類型,就要說到標簽指針(tagged pointer)了,蘋果在推出64位架構的A7雙核處理器,也就是5s的時候,為了節省記憶體和提高執行效率,蘋果提出了Tagged Pointer的概念來標註特定類型的數值。
標簽指針:標簽指針在不使用原來類型對象的情況下,把與數值有關的全部消息都放在指針值裡面。運行期系統會在消息派發期間檢測到這種標簽指針。除了NSString外,NSNumber、NSDate類型也會採用該策略。
那麼為什麼原有對象會浪費記憶體?我們可以拿NSNumber對象來舉個例子。眾所周知,NSNumber的占位與CPU的位數有關。在32位機器上整數會占4個位元組,64位上則占8個位元組。所以直接採取原有對象類型來存儲,從32位機遷移到64位機後,NSNumber的對象占用的記憶體空間就會加倍。
具體存儲策略如下圖所示:
在64位機器上,我們可以將採用標簽指針策略的對象看成由兩個部分的指針組成。第一個部分用來存儲特殊標記,表示這是一個採用了標簽指針策略的指針(不指向任何記憶體地址),第二個部分剩餘的記憶體大小都可以用作存儲數據。
但是當字元串內容超過了指針範圍後,就不會再採取標簽指針的策略了。例如初始化一個很長的德語單詞:
NSString* str=[NSString stringWithFormat:@"Kraftfahrzeughaftpflichtversicherung"];
NSLog(@"%@->%@->%@->%@:%p",[str class],[[str class] superclass],[[[str class] superclass] superclass],[[[[str class] superclass] superclass] superclass],str);
//列印如下:
__NSCFString->NSMutableString->NSString->NSObject:0x103825090
由此可知,__NSCFString繼承於NSMutableString,而NSMutableString又繼承於NSString。之所以會出現這種狀況也就是因為oc的系統框架中使用了類族的模式。一般情況下可以把該類型直接看做為NSString類型,該對象存儲於堆中。
不同類型的NSString存儲位置
類名 | 存儲位置 | 初始化引用計數 |
---|---|---|
__NSCFString | 堆 | 1 |
NSTaggedPointerString | 棧 | 2^64-1 |
__NSCFConstantString | 常量區 | 2^64-1 |
總結
- 採用語法糖創建NSString會以單例模式存儲於常量區。
- 當NSString採用stringWithFormat來創建對象時,系統會優先採取標簽指針策略來存儲對象,當對象的內容和Flag大小超出了指針大小,則採用常規方式存儲oc對象。
- 無論採用什麼方式初始化NSString,它的類型都不會是NSString類型,因為系統生成NSString的介面都是隱藏在類族中的公共介面。所以一般不會使用
[str class]==[NSString class]
或[str isMemberOfClass:[NSString class]]
來判斷str類型,而是採用[str isKindOfClass:[NSString class]]
來判斷。
參考文章:
1.NSTaggedPointerString
2.MemoryAllocation