原文鏈接: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)確。
五:__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)坑夯。
而為什么要有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