对于NSString 何时使用strong,何时使用copy,首页让我们来具体的了解一下NSString的一些内部特性,下面一篇文章总结的非常好:
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呢,一般有三种方法,现在我们就分别对这三种情况写段测试代码,如下:
12345678
NSString*str1=@"1234567890";TLog(str1);//str1: __NSCFConstantString -> 0x715ec : 1234567890 -1NSString*str2=[NSStringstringWithString:@"1234567890"];TLog(str2);//str2: __NSCFConstantString -> 0x715ec : 1234567890 -1NSString*str3=[NSStringstringWithFormat:@"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是什么情况
首先retainCount是NSUInteger的类型,其实上面的打印是将它作为int类型打印。所以它其实不是-1,它的实际值是4294967295。
在objc的retainCount中.如果对象的retainCount为这个值,就意味着“无限的retainCount”,这个对象是不能被释放的。
所有的 __NSCFConstantString对象的retainCount都为-1,这就意味着 __NSCFConstantString不会被释放,使用第一种方法创建的NSString,如果值一样,无论写多少遍,都是同一个对象。而且这种对象可以直接用==来比较
1234567
NSString*str1=@"1234567890";TLog(str1);//str1: __NSCFConstantString -> 0x715ec : 1234567890 -1NSString*str2=@"1234567890";TLog(str2);//str2: __NSCFConstantString -> 0x715ec : 1234567890 -1assert(@"abc"==@"abc");//一直正确
1.3 NSString的retain、copy和mutableCopy
我们写一段代码分别对 __NSCFConstantString 和 __NSCFString 进行retain和copy测试
__NSCFConstantString
1234567891011
NSString*str1=@"a";TLog(str1);NSString*str2=[str1retain];TLog(str2);NSString*str3=[str1copy];TLog(str3);NSString*str4=[str1mutableCopy];TLog(str4);/*str1: __NSCFConstantString -> 0x7c5e0 : a -1str2: __NSCFConstantString -> 0x7c5e0 : a -1str3: __NSCFConstantString -> 0x7c5e0 : a -1str4: __NSCFString -> 0x1559eb80 : a 1*/
上面的测试可以看出,对一个__NSCFConstantString进行retain和copy操作都还是自己,没有任何变化,对其mutableCopy操作可将其拷贝到堆上,retainCount为1.
__NSCFString
1234567891011
NSString*str1=[@"a"mutableCopy];TLog(str1);NSString*str2=[str1retain];TLog(str2);NSString*str3=[str1copy];TLog(str3);NSString*str4=[str1mutableCopy];TLog(str4);/*str1: __NSCFString -> 0x17d6d280 : a 1str2: __NSCFString -> 0x17d6d280 : a 2str3: __NSCFConstantString -> 0x3bd40090 : a -1str4: __NSCFString -> 0x17e684d0 : a 1*/
上面的测试中,我们发现,对__NSCFString进行retain和mutableCopy操作时,其特性符合正常的对象特性。但是对其copy时,它却变成了一个__NSCFConstantString对象!为了确定什么情况下才会出现这种现象我们多做一些测试
12345678910111213141516171819202122232425262728
NSString*str1=[[@"a"mutableCopy]copy];TLog(str1);NSString*str2=[NSStringstringWithFormat:@"%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 -1str2: __NSCFConstantString -> 0x3bd40090 : a -1str3: __NSCFConstantString -> 0x3bd40090 : a -1str4: __NSCFString -> 0x175ab390 : b 1str5: __NSCFString -> 0x176a5ce0 : c 1str6: __NSCFString -> 0x175ab960 : d 1str7: __NSCFString -> 0x176a5cc0 : e 1str8: __NSCFString -> 0x176a5d50 : f 1str9: __NSCFString -> 0x176a5d60 : \ 1str10: __NSCFString -> 0x176a6700 : $ 1str11: __NSCFString -> 0x175ab750 : . 1str12: __NSCFString -> 0x175ab760 : aa 1*/
起初我以为是ASCII字符比较特殊,经过上面这一段的测试发现,只有@“a”才有这样的现象,我又用模拟器测试了这一段代码,结果得到的都是_NSTaggedPointerString的对象。Tagged Pointer是一个能够提升性能、节省内存的有趣的技术。在OS X 10.10中,NSString就采用了这项技术,具体可参考这篇博客:http://blog.csdn.net/demondev/article/details/50396815
因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.
copy 此特质所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。当属性类型为 NSString 时,经常用此特质来保护其封装性。因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串。此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。
Copy-MutableCopy.png
拷贝
实现拷贝的方法有2个
copy:返回不可变副本
mutableCopy:返回可变副本
普通对象实现拷贝的步骤
遵守协议
实现-copyWithZone:方法
创建新对象
给新对象的属性赋值