原文链接:http://tutuge.me/2015/02/18/NSString%E7%9A%84Copy%E4%B8%8E%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D/
前言
不管是用什么语言开发,字符串应该算是用的最多的了。在Objective-C中,字符串类是NSString,以及其子类NSMutableString等。所以,对于字符串类,编程语言、编译器等往往会做特殊处理。下面我就说说我自己在项目中发现的NSString的“奇怪”的地方。
字符串常量,copy,mutableCopy
Example
先看看如下面的代码,猜猜会输出什么:
NSString *str1 = @"abc";
NSString *str2 = @"abc";
NSString *str3 = [[NSString alloc] initWithString:@"abc"];
NSString *str4 = [str1 copy];
NSString *str5 = [str1 mutableCopy];
//输出内存中的地址
NSLog(@"str1: %p", str1);
NSLog(@"str2: %p", str2);
NSLog(@"str3: %p", str3);
NSLog(@"str4: %p", str4);
NSLog(@"str5: %p", str5);
直接给出结果:
//地址值由运行时决定,不唯一
str1: 0x10cd12040
str2: 0x10cd12040
str3: 0x10cd12040
str4: 0x10cd12040
str5: 0x7f874b508350 //mutable copy
看,是不是很“神奇”。
简单解释
NSString是“不可变(immutable)”的类型,这个是关键。
代码中的“@”开头的字符串,其实就是一个字符串常量,运行时会检测这个字符串是否已经存在,存在的话,就直接将这个字符串的地址赋给变量;不存在的话,则创建,再赋值。
第一行: “abc”字符串不存在,创建,取得字符串地址“0x10cd12040”,赋给str1.
第二行: “abc”字符串已经存在,直接将地址“0x10cd12040”赋给str2.
第三行: (记住,NSString是不可变的)alloc并不会分配新的内存,直接将字符串地址“0x10cd12040”赋给str3.
第四行: copy,既然是不可变的,也就没有必要分配新内存,直接将地址“0x10cd12040”赋给str4.
第五行: mutableCopy其实就是创建一个NSMutableString的实例,既然是“可变(mutable)”的类型了,当然要分配新的内存,所以,字符串的地址也变成新的“0x7f874b508350”。
可以这么说,编译器、runtime充分利用了NSString是“不可变(immutable)”类型这个特点,只要字符串内容一致,就不会分配新的内存储存,字符串在程序中使用量非常大,这么做无疑可以大大节省内存,提升性能。
Property 和 NSString
先来看看一个UserEntity类:
//UserEntity.h
@interface UserEntity : NSObject <NSCopying>
@property(strong, nonatomic) NSString *name;
@end
只有一个名为name的NSString类型Property。
相信大家对这个不陌生吧。一般我们的应用中会有很多Entity类型,每个Entity类里面肯定避免不了有许多NSString的字符串类型。这里的name的属性是:strong和nonatomic,strong表明name要“持有”一份name所指的字符串的引用,nonatomic表示property类型不用加锁。
接着咱们看看下面的代码:
UserEntity *userEntity = [UserEntity new];
//创建mutable类型的字符串
NSMutableString *showName = [[NSMutableString alloc] initWithString:@"tutuge"];
//先保存“tutuge”字符串到userEntity的name
userEntity.name = showName;
//修改showName
[showName appendString:@" blog"];
//输出userEntity的name属性
NSLog(@"Name: %@", userEntity.name);
猜猜会输出什么?
Name: tutuge blog
看,我们明明是保存的“tutuge”,怎么到后面输出的是“tutuge blog”?
让NSString类型的Property为Copy型
因为name的Property属性是strong的,所以赋值后,它跟showName均指向同一个NSMutableString实例,如下:
所以改变了showName,当然name输出的值也就变了。
这个时候,name的property属性就不能是strong了,应该是copy,如下:
//UserEntity.h
@interface UserEntity : NSObject <NSCopying>
@property(copy, nonatomic) NSString *name;
@end
这样的话,当“userEntity.name = showName;”这句运行后,内存布局如下:
这样,就避免了这个问题。
总结
很所时候,代码并不是看上去那样“运行”的,多试试,多查阅资料,就能了解其中的奥秘了~