再探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
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/2562.html
標籤:iOS
上一篇:OC屬性與實體變數
