NSString 的內存問題

NSString 在 OC 的內存管理策略中是一個特殊的存在,因為其在編譯和運行中做了一些優化處理,不同于普通對象的存在,看看下面代碼打印情況(此代碼測試環境為MRC,可以在ARC工程下,設置當前測試代碼文件為手動內存管理,找到路徑Build Phases---->Compile Sources---->XXX.m,將XXX.m的Compiler Flags設置為"-fno-objc-arc".):

//宏定義
#define XWLog(_var) ({ NSString *name = @#_var; NSLog(@"變量名=%@,類型=%@, 地址=%p,值=%@,引用計數=%d", name, [_var class], _var, _var, (int)[_var retainCount]); })
    //測試代碼
    NSString *a = @"string";
    NSString *b = [[NSString alloc]init];
    NSString *c = [[NSString alloc]initWithString:@"string"];
    NSString *d = [[NSString alloc]initWithFormat:@"string"];
    NSString *e = [NSString stringWithFormat:@"string"];
    NSString *f = [NSString stringWithFormat:@"123456789"];
    NSString *g = [NSString stringWithFormat:@"1234567890"];
    XWLog(a); XWLog(b); XWLog(c); XWLog(d); XWLog(e); XWLog(f); XWLog(g);
    //打印結果
    變量名=a,類型=__NSCFConstantString, 地址=0x1015f3120,值=string,引用計數=-1
    變量名=b,類型=__NSCFConstantString, 地址=0x1019808d0,值=,引用計數=-1
    變量名=c,類型=__NSCFConstantString, 地址=0x1015f3120,值=string,引用計數=-1
    變量名=d,類型=NSTaggedPointerString, 地址=0xa00676e697274736,值=string,引用計數=-1
    變量名=e,類型=NSTaggedPointerString, 地址=0xa00676e697274736,值=string,引用計數=-1
    變量名=f,類型=NSTaggedPointerString, 地址=0xa1ea1f72bb30ab19,值=123456789,引用計數=-1
    變量名=g,類型=__NSCFString, 地址=0x60800002b580,值=1234567890,引用計數=1

從打印結果看出,變量 b 到 f 的引用計數為-1,若是無符號格式輸出,應該是一個很大的數字,與我們理解的對象初始化后引用計數為 1 所不同,不同的創建方式,字符串的類型不同,引用計數也有區別,創建的字符串有三種類型:

__NSCFConstantString
__NSCFString
NSTaggedPointerString

造成這種情況是由于 OC 對 NSString 的內存優化產生的。

  • __NSCFConstantString
    從字面就可以看出,這是一個常量字符串,該類型的字符串是以字面量創建的,是在編譯期創建的,保存在常量區。通過 a 與 c 的打印結果看出,當創建的字符串變量值在常量區存在時,變量會指向那個字符串,這是編譯期做的優化,c 指向同一字符串 @"string",地址與a相同。

文字常量區存放常量字符串,程序結束后由系統釋放,也就是說指向常量表的指針不受引用計數管理。所以對于NSCFConstantString類型的變量,OC 的內存管理策略對其無效。

  • __NSCFString
    表示這是一個對象類型的字符串,在運行時創建,存儲在堆區,服從OC 的對象內存管理策略。該類型的字符串由 Format 創建,無論是實例方法還是類方法且其長度不能太小(內容若包含中文字符,不論長度大小,都是NSCFString),否則創建的是NSTaggedPointerString類型,例如上例的變量 f 與 g。

  • NSTaggedPointerString
    對于64位程序,為了節省內存和提高運行速度,蘋果引入了 Tagged Point 技術NSTaggedPointerString是對NSCFString優化后的存在,在運行時創建時對字符串的內容和長度做出判斷,若字符串內容是由ASCII字符構成且長度較小(大概十個字符以內),這時候創建的字符串就是NSTaggedPointerString類型,字符串直接存儲在指針里,引用計數同樣為-1,不適用對象的內存管理策略。

Tagged Pointer指針的值不再是地址了,而是真正的值。所以,實際上它不再是一個對象了,它只是一個披著對象皮的普通變量而已。所以,它的內存并不存儲在堆中,OC 對象的內存管理方式對其無效。

現在,我們在看看這道題:

在MRC下,會不會造成內存泄漏?會不會奔潰?
NSString *str = [[NSString alloc] initWithString:@"ABC"];
str = @"123";
[str release];
NSLog(@"%@".str);

一目了然,str指向字符串常量,對象的內存管理方式對其無效,程序結束時,系統才會銷毀常量區的值。所以不會造成內存泄漏更不會奔潰。

接下來我們再看看NSArray的平時注意不到的問題:

    NSArray *a1 = @[@"1",@"2"];
    NSArray *a2 = [[NSArray alloc]init];
    NSArray *a3 = [[NSArray alloc]initWithObjects:@"1", nil];
    NSArray *a4 = [[NSArray alloc]initWithArray:@[@"a",@"b"]];
    NSArray *a5 = [NSArray arrayWithObjects:@"m",@"n", nil];
    NSArray *a6 = [[NSArray alloc]init];
    NSArray *a7 = @[];

    XWLog(a1); XWLog(a2); XWLog(a3); XWLog(a4); XWLog(a5); XWLog(a6);XWLog(a7);

打印結果:

變量名=a1,類型=__NSArrayI, 地址=0x608000029400,值=(1,2),引用計數=1
變量名=a2,類型=__NSArray0, 地址=0x618000012490,值=(),引用計數=-1
變量名=a3,類型=__NSSingleObjectArrayI, 地址=0x608000012890,值=(1),引用計數=1
變量名=a4,類型=__NSArrayI, 地址=0x608000029440,值=(a,b),引用計數=1
變量名=a5,類型=__NSArrayI, 地址=0x608000029460,值=(m,n),引用計數=1
變量名=a6,類型=__NSArray0, 地址=0x618000012490,值=(),引用計數=-1
變量名=a7,類型=__NSArray0, 地址=0x618000012490,值=(),引用計數=-1

從結果看出,只有a2 a6 a7這三個的引用計數為-1,其他變量的引用計數為1,顯示正常。我們仔細發現,這三個的地址相同,這就是說三個變量指向了同一塊內存,說明a2 a6 a7三個創建方式創建的空實例是一個特殊值的存在,其應該存儲在常量靜態區。我們可以猜測,OC為了優化內存,在常量靜態區創建一個空值的特殊存在,應該是靜態常量對象,無論什么方式創建的空實例,其都指向靜態區這個空值的。

不僅是NSArray,Foundation中如NSString, NSDictionary, NSSet等區分可變和不可變版本的類,空實例都是靜態對象(NSString的空實例對象是常量區的@""),對象的內存管理策略對其無效。

參考

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,366評論 11 349
  • JVM內存模型Java虛擬機(Java Virtual Machine=JVM)的內存空間分為五個部分,分別是: ...
    光劍書架上的書閱讀 2,606評論 2 26
  • 我的故鄉是一座靠近大山的小村莊,天晴的時候站在家門口就可以遠遠望見雪山的頂端高聳入云,使天地之間沒有分界。村子里常...
    彼得一世閱讀 247評論 8 25
  • 《人生七年》(7Up)一部歷時拍攝半個世紀的紀錄片,該記錄片隨機在英國選擇14個來著不同階級的孩子作為拍攝主人公,...
    顏落惜閱讀 539評論 0 2
  • 說句心里話,我不喜歡過年,總覺得過年太累。在我看來,春節,就是比平時多花很多的錢,買吃的穿的用的,以及送給別人的。...
    李唐瀚玥閱讀 404評論 0 2