我們都知道NSString
是一個Objective-C的類,但是我們有時發現它的對象在內存管理上貌似和其他的對象有一些區別。比如有時你會發現對一個NSString
進行copy
操作時,它還是原本的對象,實際上並未拷貝對象。本博客就來研究下這個問題。
為了方便測試,我先寫了個宏,用來打印NSString的isa、內存地址、值、retainCount。 注:為了了解內存特性,後面的代碼都使用了手動內存管理。
<code>#define TLog(_var) ({ NSString *name = @#_var; NSLog(@"%@: %@ -> %p : %@ %d", name, [_var class], _var, _var, (int)[_var retainCount]); }) </code>
在objc中,我們一般通過幾種方法來創建NSString呢,一般有三種方法,現在我們就分別對這三種情況寫段測試代碼,如下:
1 2 3 4 5 6 7 8
<code>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 </code>
看到上面這段測試代碼,我們可以發現幾點同我們想象不同的地方:
這裡面有幾個疑問:
其實上面第一種寫法和第二種寫法是完全一樣的,沒有任何區別,從iosSDK6開始,第二種寫法已經被遺棄了,如果用第二種寫法創建NSString,編譯器就會報一個警告。
首先retainCount是NSUInteger的類型,其實上面的打印是將它作為int類型打印。所以它其實不是-1,它的實際值是4294967295。
在objc的retainCount中.如果對象的retainCount為這個值,就意味著“無限的retainCount”,這個對象是不能被釋放的。
所有的 __NSCFConstantString對象的retainCount都為-1,這就意味著 __NSCFConstantString不會被釋放,使用第一種方法創建的NSString,如果值一樣,無論寫多少遍,都是同一個對象。而且這種對象可以直接用 ==
來比較
1 2 3 4 5 6 7
<code>NSString *str1 = @"1234567890"; TLog(str1); //str1: __NSCFConstantString -> 0x715ec : 1234567890 -1 NSString *str2 = @"1234567890"; TLog(str2); //str2: __NSCFConstantString -> 0x715ec : 1234567890 -1 assert(@"abc"==@"abc"); //一直正確 </code>
我們寫一段代碼分別對 __NSCFConstantString 和 __NSCFString 進行retain和copy測試
__NSCFConstantString
1 2 3 4 5 6 7 8 9 10 11
<code>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 */ </code>
上面的測試可以看出,對一個__NSCFConstantString進行retain和copy操作都還是自己,沒有任何變化,對其mutableCopy操作可將其拷貝到堆上,retainCount為1.
__NSCFString
1 2 3 4 5 6 7 8 9 10 11
<code>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 */ </code>
上面的測試中,我們發現,對__NSCFString進行retain和mutableCopy操作時,其特性符合正常的對象特性。但是對其copy時,它卻變成了一個__NSCFConstantString對象!為了確定什麼情況下才會出現這種現象我們多做一些測試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
<code>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 */ </code>
起初我以為是ASCII字符比較特殊,經過上面這一段的測試發現,只有@“a”才有這樣的現象,我又用模擬器測試了這一段代碼,結果得到的都是__NSCFString的對象。這個問題研究了一會,沒找到答案,暫時就放下了,好在這個對於我們編碼沒什麼影響。
問題遺留
經過這一系列的測試分析,讓我們認識了__NSCFConstantString以及它的一些特性,它是在編譯時就決定的,不能在運行時創建