iOS NSString詳解

原文鏈接:https://blog.csdn.net/qq_27909209/article/details/82150856
本文鏈接:http://www.reibang.com/p/627815e1996b

一:__NSCFConstantString睡毒, __NSCFString抢肛, NSTaggedPointerString
二:weak修飾,字符串內(nèi)存管理
三:NSTaggedPointerString講解
四:面試題
五:__NSCFString:Toll-free bridgin橋接機制

OC中的NSString不論是在編譯時還是在運行時都做了很多的優(yōu)化驳糯,并不同于普通的對象,它是一個非常復(fù)雜的存在帕识。

一:__NSCFConstantString 潜腻, __NSCFString , NSTaggedPointerString

這個證明需要再mrc環(huán)境下卡睦。
我們需要研究的NSString創(chuàng)建出來的實際的類型有:

__NSCFConstantString
__NSCFString
NSTaggedPointerString

那這三種類型分別在什么時候創(chuàng)建出來宴胧,又是什么意思呢?先看定義產(chǎn)生這三種類型分別是什么創(chuàng)建方式下產(chǎn)生的表锻∷∑耄看代碼

//宏定義
#define HXLog(_var) ({ NSString *name = @#_var; NSLog(@"變量名=%@,類型=%@瞬逊, 地址=%p显歧,引用計數(shù)=%d,值=%@", name, [_var class], _var,(int)[_var retainCount], _var); })
 
 
        NSString *a0 = [[NSString alloc] init];
        NSString *a1 = @"xiMenXiXue";
        NSString *a2 = [NSString stringWithString:@"xiMenXiXue"];
        NSString *a3 = [[NSString alloc] initWithString:@"xiMenXiXue"];
        NSString *a4 = [[NSString alloc] initWithString:a0];
 
        NSString *a5 = [[NSString alloc] initWithFormat:@"xiMenXiXue"];
        NSString *a6 = [NSString stringWithFormat:@"xiMenXiXue"];
        NSString *a7 = [NSString stringWithFormat:a5];
 
        HXLog(a0);
        HXLog(a1);
        HXLog(a2);
        HXLog(a3);
        HXLog(a4);
        HXLog(a5);
        HXLog(a6);
        HXLog(a7);

可以看出來确镊,a0追迟,a1,a2, a3, a4類型都為__NSCFConstantString類型骚腥,引用計數(shù)值為-1敦间。
a5和a7都為__NSCFString類型,引用計數(shù)值為1束铭。
a6為NSTaggedPointerString類型廓块,引用計數(shù)值為-1。

小結(jié):
創(chuàng)建的字符串有三種類型:造成這種情況是由于 OC 對 NSString 的內(nèi)存優(yōu)化產(chǎn)生的契沫。

__NSCFConstantString
從字面就可以看出带猴,這是一個常量字符串,該類型的字符串是以字面量創(chuàng)建的懈万,是在編譯期創(chuàng)建的拴清,保存在常量區(qū)。通過a0会通,a1口予,a2, a3, a4的打印結(jié)果看出,當(dāng)創(chuàng)建的字符串變量值在常量區(qū)存在時涕侈,變量會指向那個字符串沪停,這是編譯期做的優(yōu)化。

對于 initWithString 實例方法以及 stringWithString 類方法,編譯器會給出redundant警告,原因是該方法創(chuàng)建字符串等同于直接復(fù)制字符串字面量

那 retainCount為-1是什么情況

首先retainCount是NSUInteger的類型木张,其實上面的打印是將它作為int類型打印众辨。所以它其實不是-1,它的實際值是4294967295舷礼。在objc的retainCount中.如果對象的retainCount為這個值鹃彻,就意味著“無限的retainCount”,這個對象是不能被釋放的妻献。

所有的 __NSCFConstantString對象的retainCount都為-1蛛株,這就意味著 __NSCFConstantString不會被釋放,使用第一種方法創(chuàng)建的NSString旋奢,如果值一樣泳挥,無論寫多少遍,都是同一個對象至朗。而且這種對象可以直接用 == 來比較,

文字常量區(qū)存放常量字符串屉符,程序結(jié)束后由系統(tǒng)釋放,也就是說指向常量表的指針不受引用計數(shù)管理锹引。所以對于NSCFConstantString類型的變量矗钟,OC 的內(nèi)存管理策略對其無效。

__NSCFString (>=10位是__NSCFString類型)
表示這是一個對象類型的字符串嫌变,在運行時創(chuàng)建吨艇,存儲在堆區(qū),服從OC 的對象內(nèi)存管理策略腾啥。該類型的字符串由 Format創(chuàng)建东涡,無論是實例方法還是類方法且其長度不能太小(內(nèi)容若包含中文字符倘待,不論長度大小疮跑,都是NSCFString),否則創(chuàng)建的是NSTaggedPointerString類型凸舵,例如上例的變量 a5 與 a6祖娘。

NSTaggedPointerString (從上面也可以看出來,0-9位是taggedpointer類型)
對于64位程序啊奄,為了節(jié)省內(nèi)存和提高運行速度渐苏,蘋果引入了 Tagged Point 技術(shù)
NSTaggedPointerString是對NSCFString優(yōu)化后的存在菇夸,在運行時創(chuàng)建時對字符串的內(nèi)容和長度做出判斷琼富,若字符串內(nèi)容是由ASCII字符構(gòu)成且長度較小(大概十個字符以內(nèi))峻仇,這時候創(chuàng)建的字符串就是NSTaggedPointerString類型公黑,字符串直接存儲在指針里,引用計數(shù)同樣為-1摄咆,不適用對象的內(nèi)存管理策略凡蚜。

Tagged Pointer指針的值不再是地址了,而是真正的值吭从。所以朝蜘,實際上它不再是一個對象了,它只是一個披著對象皮的普通變量而已涩金。所以谱醇,它的內(nèi)存并不存儲在堆中,OC 對象的內(nèi)存管理方式對其無效步做。

對上述a0-a7進(jìn)行copy副渴,所得到的類型還都是原來的類型,并不會改變全度,并且地址都不會改變煮剧,因為原來就是一個不可變的,因為copy的還是不可變的将鸵,所以就沒有開辟新的對象勉盅。

但是進(jìn)行mutablecopy,會發(fā)現(xiàn)

__NSCFConstantString->__NSCFString; NSTaggedPointerString->__NSCFString; __NSCFString 依舊是__NSCFString類型

同時理解了上面三個類型顶掉,也就知道了一些關(guān)于string的內(nèi)存管理的些許知識草娜,比方說,看下面痒筒,用weak來修飾宰闰,看釋放的時間。

二:weak修飾簿透,字符串內(nèi)存管理

在arc環(huán)境下移袍,

__weak id weaka1 = nil;
__weak id weaka2 = nil;
__weak id weaka3 = nil;
__weak id weaka4 = nil;
 
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
 
    NSLog(@"viewWillAppear:\n weaka1:%@ \n weaka2:%@  \n weaka3:%@ \n weaka4:%@",[weaka1 class],[weaka2 class],[weaka3 class], [weaka4 class]);
}
 
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    NSLog(@"viewWillDisappear:\n weaka1:%@ \n weaka2:%@  \n weaka3:%@\n weaka4:%@",[weaka1 class],[weaka2 class],[weaka3 class], [weaka4 class]);
}
 
- (void)viewDidLoad {
    [super viewDidLoad];
 
    NSString *a1 = @"xiMenXiXue";
    NSString *a2 = [NSString stringWithFormat:@"xiMenXiXue"];
    NSString *a3 = [NSString stringWithFormat:@"xiMenXiXue strings"];
    NSString *a4 = [a1 mutableCopy];
 
    weaka1 = a1;
    weaka2 = a2;
    weaka3 = a3;
    weaka4 = a4;
}

打印結(jié)果如下:

2018-09-03 15:03:42.779454+0800 newstestt[5026:514384] viewWillAppear:
 weaka1:__NSCFConstantString 
 weaka2:NSTaggedPointerString  
 weaka3:__NSCFString 
 weaka4:(null)
2018-09-03 15:03:44.432565+0800 newstestt[5026:514384] viewWillDisappear:
 weaka1:__NSCFConstantString 
 weaka2:NSTaggedPointerString  
 weaka3:(null)
 weaka4:(null)

這個結(jié)果說明:
a1是__NSCFConstantString,字符串常量萎战,放在常量區(qū)咐容,對其retain或者release不影響它的引用計數(shù),程序結(jié)束后釋放蚂维。用字面量語法創(chuàng)建出來的string就是這種戳粒,所以在出了viewDidLoad方法以后在其他地方也能打印出值,根本沒有釋放虫啥。不由我們控制

a2是NSTaggedPointerString蔚约,Tagged Point,標(biāo)簽指針涂籽,蘋果在64位環(huán)境下對NSString和NSNumber做的一些優(yōu)化苹祟,簡單來說就是把對象的內(nèi)容存放在了指針里,這樣就不需要在堆內(nèi)存里在開辟一塊空間存放對象了,一般用來優(yōu)化長度較小的內(nèi)容树枫。這個根本不是對象直焙,所以也不受引用計數(shù)的計算。所以也無所謂釋放和不釋放之說砂轻。

a3 和a4 都是__NSCFString 類型奔誓, 這種string就和普通的對象很像了,儲存在堆上搔涝,有正常的引用計數(shù)厨喂,需要程序員分配釋放。所以weaka3 = a3時庄呈,會打印出null蜕煌,cstr出了方法作用域在runloop結(jié)束時就被autoreleasepool釋放了。只是這里有一點需要說明诬留,就是系統(tǒng)創(chuàng)建的stringWithFormat類型和我們創(chuàng)建的以 alloc, copy, init,mutableCopy和new這些方法打頭的方法斜纪,返回的都是 retained return value,例如[[NSString alloc] initWithFormat:]故响,而其他的則是unretained return value傀广,例如 [NSString stringWithFormat:]。對于前者調(diào)用者是要負(fù)責(zé)釋放的彩届,對于后者(系統(tǒng)穿件的那些)就不需要了伪冰。而且對于后者ARC會把對象的生命周期延長,確保調(diào)用者能拿到并且使用這個返回值樟蠕,但是并不一定會使用 autorelease贮聂,在worst case 的情況下才可能會使用,因此調(diào)用者不能假設(shè)返回值真的就在 autorelease pool中寨辩。從性能的角度吓懈,這種做法也是可以理解的。如果我們能夠知道一個對象的生命周期最長應(yīng)該有多長靡狞,也就沒有必要使用 autorelease 了耻警,直接使用 release 就可以。如果很多對象都使用 autorelease 的話甸怕,也會導(dǎo)致整個 pool 在 drain 的時候性能下降甘穿。

所以可以看到,a3剛開始沒多久就釋放了梢杭,但是a4過了好一會才釋放温兼。當(dāng)然這個在mrc下,自己釋放a3即可武契,因為是自己創(chuàng)建的募判。也會得到同樣的效果荡含。

三:NSTaggedPointerString講解

為什么字面量常量蘋果不使用NSTaggedPointerString呢?

【譯】采用Tagged Pointer的字符串中文版的 翻譯有些晦澀届垫,看了下英文版的描述比較易懂些:

although a string like @"a" could be stored as a tagged pointer, constant strings are never tagged pointers. Constant strings must remain binary compatible across OS releases, but the internal details of tagged pointers are not guaranteed.

原因是常量字符串需要在跨系統(tǒng)上保持二進(jìn)制兼容释液,而 tagged pointers在技術(shù)上并不能保證這個。因此對于這種短的字符串字面量還是使用\ __NSCFConstantString類型敦腔。

下面一個問題均澳,tagged pointers在內(nèi)存上分配在哪個區(qū)恨溜?
其實如果我們仔細(xì)在XCode中多點兩下符衔,就可以看到其實tagged pointers是沒有isa指針的,說明它根本不是一個對象糟袁。究其原因這個要說到tagged pointers是為什么被創(chuàng)造出來判族。

一般來說,對象所占內(nèi)存是和CPU位數(shù)相關(guān)的项戴。在32位的時候形帮,比如一個NSNumber對象占用的空間是4(對象指針)+4(對象的值)=8字節(jié),升級到64位的時候周叮,邏輯不變的話辩撑,占用的空間直接翻倍,變成8+8=16字節(jié)仿耽,這樣會產(chǎn)生十分嚴(yán)重的效率問題:為了存儲和訪問一個NSNumber對象合冀,我們需要在堆上為其分配內(nèi)存,另外還要維護(hù)它的引用計數(shù)项贺,管理它的生命期君躺。這些都給程序增加了額外的邏輯,造成運行效率上的損失开缎。

在查找資料的過程中也發(fā)現(xiàn)了蘋果官方的明確說法(摘自深入理解Tagged Pointer):
我們也可以在WWDC2013的《Session 404 Advanced in Objective-C》視頻中棕叫,看到蘋果對于Tagged Pointer特點的介紹:

Tagged Pointer專門用來存儲小的對象,例如NSNumber和NSDate
Tagged Pointer指針的值不再是地址了奕删,而是真正的值俺泣。所以,實際上它不再是一個對象了完残,它只是一個披著對象皮的普通變量而已伏钠。所以,它的內(nèi)存并不存儲在堆中坏怪,也不需要malloc和free贝润。
在內(nèi)存讀取上有著3倍的效率,創(chuàng)建時比以前快106倍铝宵。

由此看來打掘,NSTaggedPointerString根本不是對象华畏,是分配在棧區(qū)的

四:面試題

4.1:寫一個NSString類的實現(xiàn)

+ (id)initWithCString:(const char *)nullTerminatedCString encoding:(NSStringEncoding)encoding;

實現(xiàn)如下:

+ (id) stringWithCString: (const char*)nullTerminatedCString encoding:(NSStringEncoding)encoding{
  NSString  *obj;
  obj = [self allocWithZone: NSDefaultMallocZone()];
  obj = [obj initWithCString: nullTerminatedCString encoding: encoding];
  return AUTORELEASE(obj);

}

4.2:判斷兩個NSString的字面量是否相同,為什么要用isEqualToString來判斷尊蚁,而不能用==來判斷呢亡笑?
關(guān)于字符串有非常多的問題,比方說:判斷兩個NSString的字面量是否相同横朋,為什么要用isEqualToString來判斷仑乌,而不能用==來判斷呢? 可能大多數(shù)人會回答:因為==判斷的是兩個指針是否相等琴锭,而NSString是分配到堆上的晰甚,每次創(chuàng)建的時候,指針指向的地址的不同的决帖,所以不能用==來判斷厕九。但是這樣的回答不完整。這個題感覺有點毛病地回,字面量本身就只有一種方式扁远,@""; 直接創(chuàng)建等于賦值,這種方式創(chuàng)建的類型都是__NSCFConstantString刻像,本身也不會有引用計數(shù)畅买,所以它就是一個對象,這個是可以用==來判斷的细睡,我想題的意思應(yīng)該是NSString創(chuàng)建的字符串谷羞,是否相等,要用isEqualToString來判斷纹冤,因為字符串的創(chuàng)建方式不同洒宝,類型不同,地址不同萌京,單純從==來判斷的話雁歌,不準(zhǔn)確。

深入理解Tagged Pointer知残、字符串深度剖析

五:__NSCFString:Toll-free bridgin橋接機制

Toll-free bridging,簡稱為TFB靠瞎,是一種允許某些ObjC類與其對應(yīng)的CoreFoundation類(Core Foundation框架 (CoreFoundation.framework) 是一組C語言接口,它們?yōu)閕OS應(yīng)用程序提供基本數(shù)據(jù)管理和服務(wù)功能)之間可以互換使用的機制求妹。比如 NSString與CFString是橋接(bridged)的, 這意味著可以將任意NSString當(dāng)做CFString使用,也可以將任意的CFString當(dāng)做NSString使用制恍。

官網(wǎng)也有相關(guān)描述:

There are a number of data types in the Core Foundation framework and the Foundation framework that can be used interchangeably. This capability, called toll-free bridging, means that you can use the same data type as the parameter to a Core Foundation function call or as the receiver of an Objective-C message。

原理(拿NSString舉例)大概是:NSString是一個抽象類净神,每當(dāng)你創(chuàng)建一個NSString實例溉委,實際上是創(chuàng)建的NSString的一個私有子類實例。其中一個私有子類就是NSCFString,其是CFString類的在ObjC中的對應(yīng)類爱榕。NSCFString實現(xiàn)了作為NSString需要的所有方法。
我的理解:總之黔酥,你知道有Toll-Free Bridging橋接機制,然后NSCFString是NSString的私有子類跪者,實現(xiàn)了它的所有方法。詳細(xì)解釋看官網(wǎng)坑夯。

hanlin.png

而為什么要有CFString呢?
官網(wǎng)解釋

CFString provides a suite of efficient string-manipulation and string-conversion functions. It offers seamless Unicode support and facilitates the sharing of data between Cocoa and C-based programs

Toll-Free Bridging的橋接機制柜蜈、 Toll Free Bridging的內(nèi)部原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末淑履,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子藻雪,更是在濱河造成了極大的恐慌,老刑警劉巖勉耀,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異便斥,居然都是意外死亡至壤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門枢纠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來像街,“玉大人,你說我怎么就攤上這事晋渺×铮” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵木西,是天一觀的道長畴栖。 經(jīng)常有香客問我,道長八千,這世上最難降的妖魔是什么吗讶? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任挪挤,我火速辦了婚禮,結(jié)果婚禮上关翎,老公的妹妹穿的比我還像新娘扛门。我一直安慰自己,他們只是感情好纵寝,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布论寨。 她就那樣靜靜地躺著,像睡著了一般爽茴。 火紅的嫁衣襯著肌膚如雪葬凳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天室奏,我揣著相機與錄音火焰,去河邊找鬼。 笑死胧沫,一個胖子當(dāng)著我的面吹牛昌简,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绒怨,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼纯赎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了南蹂?” 一聲冷哼從身側(cè)響起犬金,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎六剥,沒想到半個月后晚顷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡疗疟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年该默,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秃嗜。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡权均,死狀恐怖叽赊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情必指,我是刑警寧澤恕洲,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站葛家,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏底燎。R本人自食惡果不足惜弹砚,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一桌吃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逗物,春花似錦、人聲如沸让簿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽田盈。三九已至,卻和暖如春允瞧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痹升。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工畦韭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人察郁。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像稳捆,于是被迫代替她去往敵國和親麦轰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360