我們都知道
NSString
是一個Objective-C
的類,但是我們有時發現它的對象在內存管理上貌似和其他的對象有一些區別。比如有時你會發現對一個NSString
進行copy
操作時,它還是原本的對象,實際上并未拷貝對象。本篇就來研究下這個問題 。
原文鏈接
1. NSString
內存管理特性分析
1.1 準備
為了方便測試,我先寫了個宏,用來打印
NSString
的isa
、內存地址、值、retainCount
。 注:為了了解內存特性,后面的代碼都使用了手動內存管理。
#define TLog(_var) ({ NSString *name = @#_var; NSLog(@"%@: %@ -> %p : %@ %d", name, [_var class], _var, _var, (int)[_var retainCount]); })
1.2 NSString
的創建
1.2.1 測試NSString
在objc中,我們一般通過幾種方法來創建NSString呢,一般有三種方法,現在我們就分別對這三種情況寫段測試代碼,如下:
NSString *str1 = @"1234567890"; TLog(str1);
//str1: __NSCFConstantString -> 0x715ec : 1234567890 -1
NSString *str2 = [NSString stringWithString:@"1234567890"]; TLog(str2);
//str2: __NSCFConstantString -> 0x715ec : 1234567890 -1
NSString *str3 = [NSString stringWithFormat:@"1234567890"]; TLog(str3);
//str3: __NSCFString -> 0x1557cb50 : 1234567890 1
看到上面這段測試代碼,我們可以發現幾點同我們想象不同的地方:
第一種方式和第二種方式創建出來的
NSString
時一模一樣的,isa
是__NSCFConstantString
,內存地址一樣,retainCount
是-1
.
第三種方式創建的
NSString
和創建其他objc
對象類似的,在堆上分配內存,初始retainCount
為1.
這里面有幾個疑問:
什么是
__NSCFConstantString
?
為什么第一種和第二種
NSString
的內存地址是一樣的?
為什么他們的
retainCount
是-1
?
1.2.2NSString
創建的寫法
其實上面第一種寫法和第二種寫法是完全一樣的,沒有任何區別,從iosSDK6
開始,第二種寫法已經被遺棄了,如果用第二種寫法創建 NSString
, 編譯器就會報一個警告。
1.2.3 retainCount
為-1
是什么情況
首先
retainCount
是NSUIntege
r的類型,其實上面的打印是將它作為int
類型打印。所以它其實不是-1
,它的實際值是4294967295
。
在objc的 retainCount 中,如果對象的retainCount為這個值,就意味著“無限的retainCount”,這個對象是不能被釋放的。
所有的 __NSCFConstantString對象的retainCount都為-1,這就意味著 __NSCFConstantString不會被釋放,使用第一種方法創建的NSString,如果值一樣,無論寫多少遍,都是同一個對象。而且這種對象可以直接用 == 來比較
NSString *str1 = @"1234567890"; TLog(str1);
//str1: __NSCFConstantString -> 0x715ec : 1234567890 -1
NSString *str2 = @"1234567890"; TLog(str2);
//str2: __NSCFConstantString -> 0x715ec : 1234567890 -1
assert(@"abc"==@"abc"); //一直正確
1.3 NSString
的retain
、copy
和mutableCopy
我們寫一段代碼分別對 __NSCFConstantString
和 __NSCFString
進行retain
和copy
測試
__NSCFConstantString
NSString *str1 = @"a"; TLog(str1);
NSString *str2 = [str1 retain]; TLog(str2);
NSString *str3 = [str1 copy]; TLog(str3);
NSString *str4 = [str1 mutableCopy]; TLog(str4);
/*
str1: __NSCFConstantString -> 0x7c5e0 : a -1
str2: __NSCFConstantString -> 0x7c5e0 : a -1
str3: __NSCFConstantString -> 0x7c5e0 : a -1
str4: __NSCFString -> 0x1559eb80 : a 1
*/
上面的測試可以看出,對一個
__NSCFConstantString
進行retain
和copy
操作都還是自己,沒有任何變化,對其mutableCopy
操作可將其拷貝到堆上,retainCount
為1
.
__NSCFString
NSString *str1 = [@"a" mutableCopy]; TLog(str1);
NSString *str2 = [str1 retain]; TLog(str2);
NSString *str3 = [str1 copy]; TLog(str3);
NSString *str4 = [str1 mutableCopy]; TLog(str4);
/*
str1: __NSCFString -> 0x17d6d280 : a 1
str2: __NSCFString -> 0x17d6d280 : a 2
str3: __NSCFConstantString -> 0x3bd40090 : a -1
str4: __NSCFString -> 0x17e684d0 : a 1
*/
上面的測試中,我們發現,對
__NSCFString
進行retain
和mutableCopy
操作時,其特性符合正常的對象特性。但是對其copy
時,它卻變成了一個__NSCFConstantString
對象!
為了確定什么情況下才會出現這種現象我們多做一些測試
NSString *str1 = [[@"a" mutableCopy] copy]; TLog(str1);
NSString *str2 = [NSString stringWithFormat:@"%s","a"]; TLog(str2);
NSString *str3 = [[[@"path/a" lastPathComponent] mutableCopy] copy]; TLog(str3);
NSString *str4 = [[@"b" mutableCopy] copy]; TLog(str4);
NSString *str5 = [[@"c" mutableCopy] copy]; TLog(str5);
NSString *str6 = [[@"d" mutableCopy] copy]; TLog(str6);
NSString *str7 = [[@"e" mutableCopy] copy]; TLog(str7);
NSString *str8 = [[@"f" mutableCopy] copy]; TLog(str8);
NSString *str9 = [[@"\\" mutableCopy] copy]; TLog(str9);
NSString *str10 = [[@"$" mutableCopy] copy]; TLog(str10);
NSString *str11 = [[@"." mutableCopy] copy]; TLog(str11);
NSString *str12 = [[@"aa" mutableCopy] copy]; TLog(str12);
/*
str1: __NSCFConstantString -> 0x3bd40090 : a -1
str2: __NSCFConstantString -> 0x3bd40090 : a -1
str3: __NSCFConstantString -> 0x3bd40090 : a -1
str4: __NSCFString -> 0x175ab390 : b 1
str5: __NSCFString -> 0x176a5ce0 : c 1
str6: __NSCFString -> 0x175ab960 : d 1
str7: __NSCFString -> 0x176a5cc0 : e 1
str8: __NSCFString -> 0x176a5d50 : f 1
str9: __NSCFString -> 0x176a5d60 : \ 1
str10: __NSCFString -> 0x176a6700 : $ 1
str11: __NSCFString -> 0x175ab750 : . 1
str12: __NSCFString -> 0x175ab760 : aa 1
*/
起初我以為是ASCII字符比較特殊,經過上面這一段的測試發現,只有@“a”
才有這樣的現象,我又用模擬器測試了這一段代碼,結果得到的都是__NSCFString
的對象。這個問題研究了一會,沒找到答案,暫時就放下了,好在這個對于我們編碼沒什么影響。
2. 小結
經過這一系列的測試分析,讓我們認識了__NSCFConstantString以及它的一些特性,它是在編譯時就決定的,不能在運行時創建。