一、NSString的内存管理机制
1、众所周知,在Objective-C中的内存管理是通过一种叫做“引用计数器”的机制管理的。举例, 当我们声明了一个新的实例:
NSData *data = [[NSData alloc] init];
NSLog(@"%ld",[data retainCount]);//打印输出为1
如果我们再次引用它的时候,他的引用计数值会+1变为2。使用完毕后需要调用[data release] ,使引用计数-1. 当该值为零的时候,系统会将data实例回收掉并释放内存。
[data release];
2、请问在这种情况下会不会造成内存泄露?为什么不会崩溃?
代码如下:
NSString *str = [[NSString alloc] initWithString:@"ABC"];
str = @"123";
[str release];
NSLog(@"%@".str);
(1)首先,咱们先对这段代码进行分析。
- 第一句 声明了一个NSString类型的实例 str, 并将其初始化init后赋值为@"ABC"
- 第二行,将str的指针指向了一个常量@"123"。 理论上讲在第一行初始化的@"ABC"没有任何任何指针指向了。 所以造成了内存泄露
- 然后第三行, 将str的引用计数-1
- 第四行输出str的值 为123.
(2)然后回答为什么不会崩溃, 因为第三行的release 实际上是release了一个常量@"123" 而作为常量,其默认的引用计数值是很大的(100k+),不信的话你们可以试试这句:
NSLog(@"retainCount = %d",[@"123" retainCount]);
(3)最终的输出值会是一个很大很大的数。 所以单单一个release是不会将其释放掉的。
(4)最后再回答这样会不会造成内存泄露。理论上是会内存泄漏的。
但是实际上,Objective-C对NSString类型有特殊照顾。所有的NSString的引用计数器默认初始值都会非常非常大。
NSString是一个不可变的字符串对象。这不是表示这个对象声明的变量的值不可变,而是表示它初始化以后,你不能改变该变量所分配的内存中的值,但你可以重新分配该变量所处的内存空间。
二、__NSCFConstantString的解释
那么__NSCFConstantString是什么呢?其实__NSCFConstantString是一个字符串常量,是没有retainCount(引用计数)的,所以没有强指针指向它,它也不会被销毁。那么,怎么获得不是__NSCFConstantString的字符串呢?
NSString *string1 = @"string 1";
NSString *string2 = [NSString stringWithString:@"string 2"];
NSString *string3 = [NSString stringWithFormat:@"string 3"];
NSString *string4 = [[NSString alloc] initWithString:@"string 4"];
NSString *string5 = [[NSMutableString alloc] initWithString:@"string"];
NSLog(@"%@:%@",string1,[string1 class]);
NSLog(@"%@:%@",string2,[string2 class]);
NSLog(@"%@:%@",string3,[string3 class]);
NSLog(@"%@:%@",string4,[string4 class]);
NSLog(@"%@:%@",string5,[string5 class]);
运行结果:
2015-04-18 10:14:35.587 内存管理[6631:201537] string 1:__NSCFConstantString
2015-04-18 10:14:35.587 内存管理[6631:201537] string 2:__NSCFConstantString
2015-04-18 10:14:35.587 内存管理[6631:201537] string 3:NSTaggedPointerString
2015-04-18 10:14:35.587 内存管理[6631:201537] string 4:__NSCFConstantString
2015-04-18 10:14:35.588 内存管理[6631:201537] string5:__NSCFString
通过以上的运行结果,发现只有使用[[NSMutableString alloc] initWithString:]得到的NSString才是__NSCFString。
三、NSString特性分析学习
我们都知道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.2 NSString创建的写法
其实上面第一种写法和第二种写法是完全一样的,没有任何区别,从iosSDK6开始,第二种写法已经被遗弃了,如果用第二种写法创建NSString,编译器就会报一个警告。
1.2.3 retainCount为-1是什么情况
1、首先retainCount是NSUInteger的类型,其实上面的打印是将它作为int类型打印。所以它其实不是-1,它的实际值是4294967295。
2、在objc的retainCount中.如果对象的retainCount为这个值,就意味着“无限的retainCount”,这个对象是不能被释放的。
3、所有的 __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测试
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.
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
*/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
*/
2. 小结
经过这一系列的测试分析,让我们认识了__NSCFConstantString以及它的一些特性,它是在编译时就决定的,不能在运行时创建。
3. NSString属性什么时候用copy,什么时候用strong?
http://southpeak.github.io/2015/05/10/ios-techset-1/