我们都知道 NSString
是一个Objective-C的类,但是我们有时候发现它的对象在内存管理上貌似和其他对象有一些区别。由于这个类是@”Hello Wrod“
的基础,所以往往忽略一些细节。让我们看看 NSString
里面一些特性
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
在OC中,我们一般通过几种方法来创建 NSString 呢,一般有三种方法,现在我们就分别对这三种情况写段测试代码,如下:
NSString *str1 = @"1234567890";
TLog(str1);
// str1: __NSCFConstantString -> 0x10455e018 : 1234567890 -1
NSString *str2 = [NSString stringWithString:@"1234567890"];
TLog(str2);
//str2: __NSCFConstantString -> 0x10455e018 : 1234567890 -1
NSString *str3 = [NSString stringWithFormat:@"1234567890"];
TLog(str3);
// str3: __NSCFString -> 0x600000330fe0 : 1234567890 1
NSString *str4 = [[NSString alloc] initWithString:@"1234567890"];
TLog(str4);
// str4: __NSCFConstantString -> 0x10455e018 : 1234567890 -1
- 第一、二、四种方式创建出来的
NSString
时一模一样的,isa 是__NSCFConstantString
,内存地址一样,retainCount 是-1。 - 第三种方式创建的
NSString
和创建其他OC对象类似的,在堆上分配内存,初始retainCount为1.
那么问题来了?
1、什么是 __NSCFConstantString
?
2、为什么会出现 retainCount 为-1?
3、为什么1、2、4这三个 NSString
对象内存地址也一样?
1.2.2 NSString创建的写法
其实上面第一种写法和第二种写法是完全一样的,没有任何区别,从 iOS SDK6 开始,第二种写法已经被遗弃了,如果用第二种写法创建 NSString
,编译器就会报方法过期的警告。
1.2.3 retainCount为-1是什么情况
首先retainCount是NSUInteger的类型,其实上面的打印是将它作为int类型打印。所以它其实不是-1,它的实际值是4294967295。
在OC的 retainCount 中.如果对象的 retainCount 为这个值,就意味着“无限的 retainCount ”,这个对象是不能被释放的。
所有的 __NSCFConstantString
对象的retainCount都为-1,这就意味着 __NSCFConstantString
不会被释放,使用第一种方法创建的 NSString
,如果值一样,无论写多少遍,都是同一个对象。而且这种对象可以直接用 == 来比较。
assert(str1 == str2); //一直正确
assert(@"abc" == @"abc"); //一直正确
1.3 NSString 的 retain、copy 和 mutableCopy
我们写一段代码分别对 __NSCFConstantString 和 __NSCFString 进行 retain 和 copy 测试
1.3.1 __NSCFConstantString
NSString *string = @"1234567890";
TLog(string);
// string: __NSCFConstantString -> 0x107bb9020 : 1234567890 -1
NSString *retainString = [string retain];
TLog(retainString);
// retainString: __NSCFConstantString -> 0x107bb9020 : 1234567890 -1
NSString *copyString = [string copy];
TLog(copyString);
// copyString: __NSCFConstantString -> 0x107bb9020 : 1234567890 -1
NSString *mutableCopyString = [string mutableCopy];
TLog(mutableCopyString);
// mutableCopyString: __NSCFString -> 0x6000003a3c00 : 1234567890 1
上面的测试可以看出,对一个__NSCFConstantString进行retain和copy操作都还是自己,没有任何变化,对其mutableCopy操作可将其拷贝到堆上,retainCount为1.
1.3.2 __NSCFString
NSString *string = [NSString stringWithFormat:@"1234567890"];
TLog(string);
// string: __NSCFString -> 0x600000a494e0 : 1234567890 1
NSString *retainString = [string retain];
TLog(retainString);
// retainString: __NSCFString -> 0x600000a494e0 : 1234567890 2
NSString *copyString = [string copy];
TLog(copyString);
// copyString: __NSCFString -> 0x600000a494e0 : 1234567890 3
NSString *mutableCopyString = [string mutableCopy];
TLog(mutableCopyString);
// mutableCopyString: __NSCFString -> 0x60000046ab80 : 1234567890 1
上面的测试中,我们发现,对__NSCFString
进行retain和mutableCopy操作时,其特性符合正常的对象特性。但是对其copy时,它却变成了一个__NSCFConstantString
对象!copy 会使原来的对象引用计数加一,并拷贝对象地址给新的指针。
mutableCopy 不会改变引用计数,会拷贝内容到堆上,生成一个 __NSCFString
对象,新对象的引用计数为1。
1.3.3 __NSTaggedPointerString
NSString *string = [NSString stringWithFormat:@"a"];
TLog(string);
// string: NSTaggedPointerString -> 0xb2590670e6f2b9aa : a -1
NSString *retainString = [string retain];
TLog(retainString);
// retainString: NSTaggedPointerString -> 0xb2590670e6f2b9aa : a -1
NSString *copyString = [string copy];
TLog(copyString);
// copyString: NSTaggedPointerString -> 0xb2590670e6f2b9aa : a -1
NSString *mutableCopyString = [string mutableCopy];
TLog(mutableCopyString);
// mutableCopyString: __NSCFString -> 0x600002647cf0 : a 1
这回我们字符串定义成一个字符长度的的字符串“a”
我们发现怎么类型又变成 NSTaggedPointerString
。而且跟 NSCFConstantString
一样计数器都是-1。
2. 小结
结果是很复杂的,按照产生对象的isa大致可以分为三种情况:
产生的对象是 __NSCFConstantString
产生的对象是 __NSCFString
产生的对象是 __NSTaggedPointerString
__NSCFConstantString
跟 NSTaggedPointerString
计数器为-1,因为 NSString
内部做了某种优化。
类型 | 引用计数 |
---|---|
__NSCFString | 1 |
NSTaggedPointerString、__NSCFConstantString | -1 |
2.1 三种类型分别是什么,分别是在什么情况下产生的,分别处于内存的那个区域?
__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
类型。
这种对象被直接存储在指针的内容中,可以当作一种伪对象。
网友评论