1. 程式人生 > >NSString的記憶體管理之 __NSCFConstantString、NSTaggedPointerString、__NSCFString

NSString的記憶體管理之 __NSCFConstantString、NSTaggedPointerString、__NSCFString

by skyfly

在 Objective-C 的 Fondation 框架中 NSString 物件是很複雜的存在,各種方式建立以及不同長度的字串都會影響 NSString 物件在記憶體中所處的位置。Objective-C 在執行時也對其做了很多優化。今天就來研究一下 NSString 這個複雜的物件。

構建一些測試程式碼:

為了觀察 NSString 的記憶體管理情況,我選擇關閉 ARC 使用 MRC 來進行測試。以觀察其引用計數等狀況。

先寫一個 Log 巨集。

1

#define TLog(_var) ({ NSString *name = @#_var; NSLog(@"%@: %@ -> %p : %@ %lu", name, [_var class], _var, _var, [_var retainCount]); })

NSString揭祕

測試程式碼如下:

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

29

NSString *str1 = @"sa";

TLog(str1);

//str1: __NSCFConstantString -> 0x100001050 : sa 18446744073709551615

NSString *str2 = [NSString stringWithString:@"sa"];

TLog(str2);

//str2: __NSCFConstantString -> 0x100001050 : sa 18446744073709551615

NSString *str3 = @"1234567890";

TLog(str3);

//str3: __NSCFConstantString -> 0x100001110 : 1234567890 18446744073709551615

NSString *str4 = [NSString stringWithFormat:@"sa"];

TLog(str4);

//str4: NSTaggedPointerString -> 0x617325 : sa 18446744073709551615

NSString *str5 = [NSString stringWithFormat:@"sa"];

TLog(str5);

//str5: NSTaggedPointerString -> 0x617325 : sa 18446744073709551615

NSString *str6 = [NSString stringWithFormat:@"123456789"];

TLog(str6);

//str6: NSTaggedPointerString -> 0x1ea1f72bb30ab195 : 123456789 18446744073709551615

NSString *str7 = [NSString stringWithFormat:@"1234567890"];

TLog(str7);

//str7: __NSCFString -> 0x100300800 : 1234567890 1

結果是很複雜的,按照產生物件的isa大致可以分為三種情況:

  1. 產生的物件是 __NSCFConstantString
  2. 產生的物件是 __NSCFString
  3. 產生的物件是 NSTaggedPointerString

而且可以看到,在 MRC 下的引用計數也是不盡相同的:

引用計數 型別
1 __NSCFString
18446744073709551615(2^64-1) NSTaggedPointerString, __NSCFConstantString

這樣的話就提出了幾個疑問:

  • 三種類型分別是什麼,有什麼不同?
  • 三種類型的字串指標分別是在什麼情況下產生的?
  • 三種類型的字串分別處於記憶體的那個區域?
  • 引用計數為什麼會是18446744073709551615?

三種類型分別是什麼,分別是在什麼情況下產生的,分別處於記憶體的那個區域?

__NSCFConstantString

字串常量,是一種編譯時常量,它的 retainCount 值很大,是 4294967295,在控制檯打印出的數值則是 18446744073709551615==2^64-1,測試證明,即便對其進行 release 操作,retainCount 也不會產生任何變化。是建立之後便是放不掉的物件。相同內容的 __NSCFConstantString 物件的地址相同,也就是說常量字串物件是一種單例。

這種物件一般通過字面值 @"..."CFSTR("...") 或者 stringWithString: 方法(需要說明的是,這個方法在 iOS6 SDK 中已經被稱為redundant,使用這個方法會產生一條編譯器警告。這個方法等同於字面值建立的方法)產生。

這種物件儲存在字串常量區。

__NSCFString

和 __NSCFConstantString 不同, __NSCFString 物件是在執行時建立的一種 NSString 子類,他並不是一種字串常量。所以和其他的物件一樣在被建立時獲得了 1 的引用計數。

通過 NSString 的 stringWithFormat 等方法建立的 NSString 物件一般都是這種型別。

這種物件被儲存在堆上。

NSTaggedPointerString

理解這個型別,需要明白什麼是標籤指標,這是蘋果在 64 位環境下對 NSString,NSNumber 等物件做的一些優化。簡單來講可以理解為把指標指向的內容直接放在了指標變數的記憶體地址中,因為在 64 位環境下指標變數的大小達到了 8 位足以容納一些長度較小的內容。於是使用了標籤指標這種方式來優化資料的儲存方式。從他的引用計數可以看出,這貨也是一個釋放不掉的單例常量物件。在執行時根據實際情況建立。

對於 NSString 物件來講,當非字面值常量數字,英文字母字串的長度小於等於 9 的時候會自動成為 NSTaggedPointerString 型別,如果有中文或其他特殊符號(可能是非 ASCII 字元)存在的話則會直接成為 )__NSCFString 型別。

這種物件被直接儲存在指標的內容中,可以當作一種偽物件。

0x01 引用計數為什麼會是18446744073709551615?

這個值意味著無限的retainCount,這個物件是不能被釋放的。 所有的 NSCFConstantString物件的retainCount都是這個值,這就意味著 NSCFConstantString不會被釋放,使用第一種方法建立的NSString,如果值一樣,無論寫多少遍,都是同一個物件。而且這種物件可以直接用 == 來比較。

分析NSString的 copy,retain,mutableCopy表現

測試程式碼如下:

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

29

30

31

32

33

34

35

36

37

NSString *testOutput;

NSString *str9 = @"as";

TLog(str9);

TLog(str9);

[str9 retain];

TLog(str9);

NSString *str = [str9 copy];

TLog(str);

TLog(str9);

str = [str9 mutableCopy];

TLog(str);

TLog(str9);

NSString *str10 = [NSString stringWithFormat:@"as"];

TLog(str10);

[str10 retain];

TLog(str10);

str = [str10 copy];

TLog(str);

TLog(str10);

str = [str10 mutableCopy];

TLog(str);

TLog(str10);

NSString *str11 = [NSString stringWithFormat:@"1234567890"];

TLog(str11);

[str11 retain];

TLog(str11);

str = [str11 copy];

TLog(str);

TLog(str11);

str =[str11 mutableCopy];

TLog(str);

TLog(str11);

實驗證明:copy 會使原來的物件引用計數加一,並拷貝物件地址給新的指標。 mutableCopy 不會改變引用計數,會拷貝內容到堆上,生成一個 __NSCFString 物件,新物件的引用計數為1.