前兩天跟同事爭論一個關於NSString執行copy操作以后是否會發生變化,兩個人整了半天,最后寫代碼驗證了一下,發現原來NSString操作沒我們想的那么簡單,下面就讓我們一起看看NSString和NSMutableString在MRC下執行retain,copy,mutableCopy,以及ARC下不同的修飾__weak, __strong修飾賦值究竟發生了什么。
一、驗證代碼如下:
- (void)testStringAddress { int a = 0; int b = 0; static int c = 0; NSString *str = @"Hello World"; #if __has_feature(objc_arc) __weak NSString *weakStr = str; __strong NSString *strongStr = str; #else NSString *retainStr = [str retain]; #endif NSString *copyStr = [str copy]; NSMutableString *mutableCopyStr = [str mutableCopy]; // 驗證mutableCopy出來的是否是mutableString,如果不是執行此行會Crash [mutableCopyStr appendFormat:@".."]; str = @"i'm changed"; NSString *str2 = [NSString stringWithFormat:@"Hello world"]; #if __has_feature(objc_arc) __weak NSString *weakStr2 = str2; __strong NSString *strongStr2 = str2; #else NSString *retainStr2 = [str2 retain]; #endif NSString *copyStr2 = [str2 copy]; NSString *copy2Str2 = [str2 copy]; NSString *mutableCopyStr2 = [str2 mutableCopy]; NSString *mutableCopy2Str2 = [str mutableCopy]; str2 = [[NSString alloc] initWithFormat:@"changed"]; NSMutableString *mutableStr = [NSMutableString stringWithString:@"hello world"]; #if __has_feature(objc_arc) __weak NSMutableString *weakMutableStr = mutableStr; __strong NSMutableString *strongMutableStr = mutableStr; #else NSMutableString *retainMutableStr = [mutableStr retain]; #endif NSMutableString *copyMutableStr = [mutableStr copy]; NSMutableString *copy2MutableStr = [mutableStr copy]; NSString *mutableCopyMutableStr = [mutableStr mutableCopy]; NSString *mutableCopy2MutableStr = [mutableStr mutableCopy]; [mutableStr appendFormat:@" apped something"]; #if __has_feature(objc_arc) NSLog(@"\r str: %@,\r weakStr: %@,\r strongStr: %@,\r copyStr: %@,\r mutableCopyStr: %@", str, weakStr, strongStr, copyStr, mutableCopyStr); NSLog(@"\r str2: %@,\r weakStr2: %@,\r strongStr: %@,\r copyStr2: %@,\r mutableCopyStr2: %@", str2, weakStr2, strongStr2, copyStr2, mutableCopyStr2); NSLog(@"\r mutableStr: %@,\r weakMutableStr: %@\r strongMutableStr: %@,\r copyMutableStr: %@,\r mutableCopyMutableStr: %@", mutableStr, weakMutableStr, strongMutableStr, copyMutableStr, mutableCopyMutableStr); #else NSLog(@"\r str: %@,\r retainStr: %@,\r copyStr: %@,\r mutableCopyStr: %@", str, retainStr, copyStr, mutableCopyStr); NSLog(@"\r str2: %@,\r retainStr2: %@,\r copyStr2: %@,\r mutableCopyStr2: %@", str2, retainStr2, copyStr2, mutableCopyStr2); NSLog(@"\r mutableStr: %@,\r retainMutableStr: %@,\r copyMutableStr: %@,\r mutableCopyMutableStr: %@", mutableStr, retainMutableStr, copyMutableStr, mutableCopyMutableStr); #endif }
代碼中最開始定義了兩個int型的變量,主要是為了打印出當前函數的棧地址,順便驗證了一下內存中棧是從高地址向低地址生長的。使用預編譯宏#if __has_feature(objc_arc)來檢測當前是否是ARC環境,並根據不同環境寫了不同的測試代碼。
通過在testStringAddress最后添加斷點,在lldb命令行下通過“p”命令輸出變量對應的地址,例如:查看str指向的內存地址和內容,輸入"p str"即可。
調試環境xCode6 beta4,iOS8 SDK。
二、MRC下執行情況
執行結果如下圖所示:

解釋一下上圖的內容:
1、第一部分
“p &str”表示打印str這個變量本身的地址,“p str”表示打印str指向的內容的地址。如上圖所示,"p &str"打印出來的結果是0xbff0aff8,局部變量b的地址為0xbff0affc,我們可以看出這兩者的內存地址是相連,因為他們都是函數體內部局部變量。而“p str”打印出來的結果是0x000a7048,說明str指向的內容的存儲區域和函數局部變量不在一起。
2、第二部分
c是我在函數中定義的一個static變量,“p &c”打印出來的地址為0x000a778c,觀察可知變量c和函數變量也不在一起。static變量在程序運行過程中只會有一個,位於程序的靜態變量區。
3、第三部分
str的定義為“NSString *str = @"Hello World";”,通過調試發現str指向的內容為0x000a7048,且此時對str執行的retain和copy操作得到的結果地址均為0x000a7028。而執行mutableCopy后得到的NSString的地址為0x7974a110,和str以及retain,copy操作得到的地址不在一起。
總結:形如@“Hello World”形式的變量在程序的常量區,而NSString在針對常量區的對象首次執行retain和copy時創建新對象,之后執行同類操作則發揮之前創建的對象,mutableCopy操作則在其他地方創建了一個對象,並使用str完成了初始化操作。
4、第四部分
str2定義為“NSString *str2 = [NSString stringWithFormat:@"Hello world"];”,打印結果發現str2指向的內容的地址為0x79749000,執行retain操作得到的string的指向內容的地址為0x7974a5c0,copy操作得到的地址也是0x7974a5c0,測試發現之后再對str2執行copy操作均會得到相同的地址。這塊兒貌似跟我們平時常念的“retain增加引用技術,copy創建新的對象”的觀念不符。對str2執行mutableCopy得到的地址為0x7974a380,重新創建了一個對象,符合我們的預期。
總結:使用stringWithFormat方式創建的NSString對象默認在堆上,對這一類對象首次執行retain或copy操作時會創建一份拷貝,后續所有的retain和copy均會指向之前創建的同一個拷貝,無論何時執行mutableCopy操作均會創建新的對象。
5、第五部分
mutableStr的定義為“[NSMutableString stringWithString:@"hello world"];”,打印結果發現對mutableStr執行retain操作得到對象的地址和mutableStr相同,執行copy操作會創建新的對象,執行mutableCopy操作也會創建新的對象。
總結:使用stringWithString方式創建的NSMutableString對象默認在堆上,對NSMutableString執行retain時不會創建新對象,執行copy和mutableCopy均會創建新的對象。
觀察以上對象地址,大致可以分為四個區間0x000a70xx,0x000a78xx,0x7974xxxx,0xbff5xxxx,其實它們分別依次代表四個不同的內存段常量區,靜態變量區,堆區,棧區。
三、ARC下執行情況
執行結果如下圖:

分析方法和第一部分一樣,這里就不重復了。
總結:
1、針對常量區的NSString對象,執行weak,strong賦值活着copy操作只會生成一份拷貝,每次執行mutableCopy時均會創建新的對象。
2、針對堆上的NSString對象執行weak,strong,copy操作時只會創建一份拷貝,后續所有操作均得到相同的對象,每次執行mutableCopy時均會創建新的對象。
3、針對NSMutableString對象首次執行weak,strong操作只會創建一份拷貝,后續所有操作均得到相同對象,每次執行copy和mutableCopy操作均會創建新的對象。
四、總結
綜上ARC和MRC下NSString,NSMutableString執行retain,copy,mutableCopy,weak,strong操作時內存情況見下表格:


注:smileEvday保留本文的一切權利
轉載請著名出處,有什么問題歡迎留言
如果覺得本文幫到了你,請推薦給身邊的朋友
