NSString的Copy与内存分配

前言

不管是用什么语言开发,字符串应该算是用的最多的了。在Objective-C中,字符串类是NSString,以及其子类NSMutableString等。所以,对于字符串类,编程语言、编译器等往往会做特殊处理。下面我就说说我自己在项目中发现的NSString的“奇怪”的地方。

字符串常量,copy,mutableCopy

Example

先看看如下面的代码,猜猜会输出什么:

1
2
3
4
5
6
7
8
9
10
11
12
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);

直接给出结果:

1
2
3
4
5
6
//地址值由运行时决定,不唯一
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类:

1
2
3
4
5
//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类型不用加锁。

接着咱们看看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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);

猜猜会输出什么?

1
Name: tutuge blog

看,我们明明是保存的“tutuge”,怎么到后面输出的是“tutuge blog”?

让NSString类型的Property为Copy型

因为name的Property属性是strong的,所以赋值后,它跟showName均指向同一个NSMutableString实例,如下:

内存示意图

所以改变了showName,当然name输出的值也就变了。
这个时候,name的property属性就不能是strong了,应该是copy,如下:

1
2
3
4
5
//UserEntity.h

@interface UserEntity : NSObject <NSCopying>
@property(copy, nonatomic) NSString *name;
@end

这样的话,当“userEntity.name = showName;”这句运行后,内存布局如下:

内存示意图

这样,就避免了这个问题。

总结

很所时候,代码并不是看上去那样“运行”的,多试试,多查阅资料,就能了解其中的奥秘了~

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器