文章目录:
三种类型:
代码示例:
(注:本文中的测试均在ARC环境下进行)
NSString *stringFirst = @"1234567890";
NSLog(@"%@ -> %p : %@ %ld", [stringFirst class], stringFirst, stringFirst, CFGetRetainCount((__bridge CFTypeRef)(stringFirst)));
NSString *stringSecond = [NSString stringWithFormat:@"1234567890"];
NSLog(@"%@ -> %p : %@ %ld", [stringSecond class], stringSecond, stringSecond, CFGetRetainCount((__bridge CFTypeRef)(stringSecond)));
NSString *stringThird = [NSString stringWithFormat:@"sa"];
NSLog(@"%@ -> %p : %@ %ld", [stringThird class], stringThird, stringThird, CFGetRetainCount((__bridge CFTypeRef)(stringThird)));
NSString *stringFourth = @"sa";
NSLog(@"%@ -> %p : %@ %ld", [stringFourth class], stringFourth, stringFourth, CFGetRetainCount((__bridge CFTypeRef)(stringFourth)));
我们来看一下打印结果:
我们清晰地看到NSString一共有三种类型,接下来就详细介绍这三种类型。
_NSCFconstantString
名字中的constant是常量的意思
字符串常量,是一种编译时
常量,它的 retainCount
值很大,本例打印的值为1152921504606846975
说明它是无限的retainCount,这个对象是不能释放的,测试证明,即便对其进行 release
操作,retainCount
也不会产生任何变化。是创建之后便是放不掉的对象。相同内容的 __NSCFConstantString
对象的地址相同,也就是说常量字符串对象是一种单例。
这种对象一般通过字面值 @"..."
、 stringWithString:
方法 或者CFSTR("...")
方法(需要说明的是,这个方法在 iOS6 SDK 中已经被称为redundant,使用这个方法会产生一条编译器警告。这个方法等同于字面值创建的方法)产生。
这种对象存储在字符串常量区
。
_NSCFString
和 __NSCFConstantString
不同, __NSCFString
对象是在运行时创建的一种 NSString
子类,他并不是一种字符串常量。所以和其他的对象一样在被创建时获得了 1
的引用计数。
通过 NSString
的 stringWithFormat
等方法创建的 NSString
对象一般都是这种类型。
这种对象被存储在堆
上。
NSTaggedPointerString
理解这个类型,需要明白什么是标签指针
,这是苹果在 64 位
环境下对 NSString
,NSNumber
等对象做的一些优化。简单来讲可以理解为把指针指向的内容直接放在了指针变量的内存地址中,因为在 64 位
环境下指针变量
的大小达到了 8 位
足以容纳一些长度较小的内容。于是使用了标签指针这种方式来优化数据的存储方式。从他的引用计数
可以看出,这个也是一个释放不掉的单例常量对象
。在运行时根据实际情况创建。
对于 NSString
对象来讲,当非字面值常量的数字,英文字母字符串的长度小于等于 9
的时候(若字符串中含q
字母的话就是长度小于等于7
)会自动成为 NSTaggedPointerString
类型,如果有中文或其他特殊符号(可能是非 ASCII 字符)存在的话则会直接成为 )__NSCFString
类型。
这种对象被直接存储在指针
的内容中,可以当作一种伪对象。
TaggedPointer是一个iOS针对与内存优化的一种骚操作,其关于NSString
的应用大家可以详见大佬博客:iOS里的TaggedPointer[NSString篇]
三种情况的copy、mutableCopy操作后的表现:
_NSCFconstantString
代码:
//创建一个_NSCFconstantString对象
NSString *stringFirst = @"sa";
NSLog(@"%@ -> %p : %@ %ld", [stringFirst class], stringFirst, stringFirst, CFGetRetainCount((__bridge CFTypeRef)(stringFirst)));
//copy
NSString *stringTestFirst = [stringFirst copy];
NSLog(@"%@ -> %p : %@ %ld", [stringTestFirst class], stringTestFirst, stringTestFirst, CFGetRetainCount((__bridge CFTypeRef)(stringTestFirst)));
NSLog(@"%@ -> %p : %@ %ld", [stringFirst class], stringFirst, stringFirst, CFGetRetainCount((__bridge CFTypeRef)(stringFirst)));
//mutableCopy
stringTestFirst = [stringFirst mutableCopy];
NSLog(@"%@ -> %p : %@ %ld", [stringTestFirst class], stringTestFirst, stringTestFirst, CFGetRetainCount((__bridge CFTypeRef)(stringTestFirst)));
NSLog(@"%@ -> %p : %@ %ld", [stringFirst class], stringFirst, stringFirst, CFGetRetainCount((__bridge CFTypeRef)(stringFirst)));
打印结果:
我们发现,copy
是拷贝对象的地址给新的指针,但不会改变原有对象的引用计数。mutableCopy
也不会改变原有对象的引用计数,但会拷贝内容到堆上,生成一个新的__NSCFString
对象,新对象的引用计数值为1
。
_NSCFString
代码:
//创建一个_NSCFString对象
NSString *stringSecond = [NSString stringWithFormat:@"1234567890"];
NSLog(@"%@ -> %p : %@ %ld", [stringSecond class], stringSecond, stringSecond, CFGetRetainCount((__bridge CFTypeRef)(stringSecond)));
//copy
NSString *stringTestSecond = [stringSecond copy];
NSLog(@"%@ -> %p : %@ %ld", [stringTestSecond class], stringTestSecond, stringTestSecond, CFGetRetainCount((__bridge CFTypeRef)(stringTestSecond)));
NSLog(@"%@ -> %p : %@ %ld", [stringSecond class], stringSecond, stringSecond, CFGetRetainCount((__bridge CFTypeRef)(stringSecond)));
//mutableCopy
stringTestSecond = [stringSecond mutableCopy];
NSLog(@"%@ -> %p : %@ %ld", [stringTestSecond class], stringTestSecond, stringTestSecond, CFGetRetainCount((__bridge CFTypeRef)(stringTestSecond)));
NSLog(@"%@ -> %p : %@ %ld", [stringSecond class], stringSecond, stringSecond, CFGetRetainCount((__bridge CFTypeRef)(stringSecond)));
打印结果:
我们发现,copy
会使原来的对象引用计数加1
,并拷贝对象地址给新的指针。mutableCopy不会改变原有对象的引用计数,但会拷贝内容到堆上,生成一个新的__NSCFString
对象,新对象的引用计数为1
。
NSTaggedPointerString
代码:
//创建一个NSTaggedPointerString对象
NSString *stringThird = [NSString stringWithFormat:@"sa"];
NSLog(@"%@ -> %p : %@ %ld", [stringThird class], stringThird, stringThird, CFGetRetainCount((__bridge CFTypeRef)(stringThird)));
//copy
NSString *stringTestThird = [stringThird copy];
NSLog(@"%@ -> %p : %@ %ld", [stringTestThird class], stringTestThird, stringTestThird, CFGetRetainCount((__bridge CFTypeRef)(stringTestThird)));
NSLog(@"%@ -> %p : %@ %ld", [stringThird class], stringThird, stringThird, CFGetRetainCount((__bridge CFTypeRef)(stringThird)));
//mutableCopy
stringTestThird = [stringThird mutableCopy];
NSLog(@"%@ -> %p : %@ %ld", [stringTestThird class], stringTestThird, stringTestThird, CFGetRetainCount((__bridge CFTypeRef)(stringTestThird)));
NSLog(@"%@ -> %p : %@ %ld", [stringThird class], stringThird, stringThird, CFGetRetainCount((__bridge CFTypeRef)(stringThird)));
打印结果:
我们可以看到,NSTaggedPointerString
类型的字符串copy
、mutableCopy
操作结果和_NSCFconstantString
类型的字符串十分相似,因为两者都是单例
形式。
结果是:copy
是拷贝对象的地址给新的指针,但不会改变原有对象的引用计数。mutableCopy
也不会改变原有对象的引用计数,但会拷贝内容到堆上,生成一个新的__NSCFString
对象,新对象的引用计数值为1
。
这里有一个有意思的点,就是通过打印NSTaggedPointerString类型的字符串的地址我们可以发现,它们的地址的最后一位永远是奇数
,其实这个最后一位的奇数就是NSTaggedPointerString
类型的标志。
内存补充:
我们来看一下内存的分布:
__NSCFConstantString:
//创建一个_NSCFconstantString对象
NSString *stringFirst = @"sa";
我们从上图的:
可以看到,输出的地址为:0x100c8f078
内存分布图中:0~8字节–>20 17 D4 09 01 00 00 00
我们好奇这8个字节是什么:
这下我们就知道前8个字节是isa指针
9~16字节–>C8 07 00 00 00 00 00 00
测试发现每个字符串对象都有这个
17~24字节–>DA E2 C8 00 01 00 00 00
,这个也不知道是什么,然后我们通过打印NSLog(@"%p", "sa");
这个单例对象的地址发现它的地址是0x100c8e2da
刚刚好产生了对应,然后我们去0x100c8e2da
这个地址看一看:
发现ASCII码值与“sa”对应
25~32字节->02 00 00 00 00 00 00 00
存放的是字符串的长度。
对于__NSCFConstantString,我们向查看内存分布情况,直接打印stringFirst得到的是stringFirst这个指针的地址信息,前8位是isa指针,17到24位是对应常量字符串的地址,25~32位是字符串的长度。
__NSCFString:
与__NSCFConstantString的存储常量的地址不同
__NSCFString直接将对应字符串的ASCII码存储在之前17~24字节存储对应字符串地址的地方,而不是通过再存一个地址来进行存储。
对于常量字符串的单例来说,仅仅存储地址,哪怕后面再创建新的字符串,但是只要内容相同,stringFirst对象里面存储的该字符串的地址都是一样的。而对于CFString来说,每个对象都是新的,每个对象都是由自己内部的地址来直接存储,省略了再次通过地址获取内容的步骤。大家哪怕内容相同,自己也是自己的。
最后对于NSTaggedPointerString类型的内存分布此处就不多赘述,详见上方链接的大佬博客。