NSString的三种形式
引入
__NSCFConstantString
、__NSCFString
和 NSTaggedPointerString
都是 NSString
的内部实现方式,用于表示不同类型的字符串对象。它们在内存中的存储和处理方式有所不同。
运行结果
__NSCFConstantString
__NSCFConstantString
是 NSString
的一种实现方式,用于表示不可变的字符串常量。它是在编译时创建的,存储在常量区(const section)中,并且在程序生命周期内是不可修改的。由于是常量,多个相同的字符串常量在内存中只会存储一份,以节省内存空间。
- 它是编译期间的常量字符串,存储在只读的数据段中,因此具有固定的内存地址。
- 这种字符串类型通常用于静态字符串的表示,比如使用
@"Hello"
这样的字符串字面量。
从他的引用计数来看,是一个很大的数,所以可以理解为是不可以被释放的,而且只在内存之中存储一份,所以我们可以理解为其为一种单例的形式
对于__NSCFConstantString
创建我们可以使用@" "
或者stringWithString
跟一个常量字符串来进行创建。
__NSCFString
__NSCFString
是 NSString
的另一种实现方式,用于表示不可变的字符串对象。它是基于动态分配的内存,可以包含任意长度的字符序列。当你使用字符串字面量或调用 initWithFormat:
、stringWithFormat:
等方法创建不可变字符串时,会生成一个 __NSCFString
对象。在创建的时候,会获得一次引用计数,说明该对象是可以被释放的。
从str1
以及str1_
的结果我们可以看到,这种类型的字符串被创建在堆上,即使内容相同的字符串也会重新为其创建对象,用不同的指针指向的也是不同的对象
NSTaggedPointerString
标记指针(Tagged Pointer)是一种优化技术,通过提高空间利用率和访问效率来改善程序的性能和效率。用于在不分配额外内存的情况下存储小的对象或数字值。这是苹果在 64 位环境下对 NSString
,NSNumber
等对象做的一些优化。**简单来讲当对象的大小小于一个指针的大小时,可以直接将数据存储在指针中,而不需要额外的内存分配。**因为在 64 位环境下,OC的相关变量指针变量的大小达到了 8位,这个大小足以容纳一些长度较小的内容。于是就使用了标签指针这种方式来优化数据的存储方式。
当数字、英文字母字符串的长度小于等于 9 且不包含中文字符或其他特殊符号时,它们会自动成为 NSTaggedPointerString
类型,以节省内存并提高访问速度。而一旦字符串中包含中文字符或其他特殊符号,它们将被存储为 __NSCFString
类型,需要额外的内存分配。这种方式避免了内存分配和释放的开销,提高了程序的性能和内存利用率。
三种字符串的复制情况
为了测试以上三种字符串使用copy
和mutableCopy
的结果,我们使用以下程序
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *str1 = @"Hello";
NSString *str2 = [NSString stringWithFormat:@"Hello,world"];
NSString *str3= [NSString stringWithFormat:@"World"];
// copy
NSString *str1ShallowCopy = [str1 copy];
NSString *str2ShallowCopy = [str2 copy];
NSString *str3ShallowCopy = [str3 copy];
//使用stringWithString方法
NSString *str1_ = [NSString stringWithString:str1];
NSString *str2_ = [NSString stringWithString:str2];
NSString *str3_ = [NSString stringWithString:str3];
// mutablecopy
NSMutableString *str1MutableDeepCopy = [str1 mutableCopy];
NSMutableString *str2MutableDeepCopy = [str2 mutableCopy];
NSMutableString *str3MutableDeepCopy = [str3 mutableCopy];
NSLog(@"原始字符串:");
NSLog(@"str1 = %p, %@, %lu", str1, [str1 class], [str1 retainCount]);
NSLog(@"str2 = %p, %@, %lu", str2, [str2 class], [str2 retainCount]);
NSLog(@"str3 = %p, %@, %lu", str3, [str3 class], [str3 retainCount]);
NSLog(@"浅拷贝:");
NSLog(@"str1ShallowCopy = %p, %@, %lu", str1ShallowCopy, [str1ShallowCopy class], [str1ShallowCopy retainCount]);
NSLog(@"str2ShallowCopy = %p, %@, %lu", str2ShallowCopy, [str2ShallowCopy class], [str2ShallowCopy retainCount]);
NSLog(@"str3ShallowCopy = %p, %@, %lu", str3ShallowCopy, [str3ShallowCopy class], [str3ShallowCopy retainCount]);
NSLog(@"stringWithString:");
NSLog(@"str1_ = %p, %@, %lu", str1_, [str1_ class], [str1_ retainCount]);
NSLog(@"str2_ = %p, %@, %lu", str2_, [str2_ class], [str2_ retainCount]);
NSLog(@"str3_ = %p, %@, %lu", str3_, [str3_ class], [str3_ retainCount]);
NSLog(@"深拷贝:");
NSLog(@"str1MutableDeepCopy = %p, %@, %lu", str1MutableDeepCopy, [str1MutableDeepCopy class], [str1MutableDeepCopy retainCount]);
NSLog(@"str2MutableDeepCopy = %p, %@, %lu", str2MutableDeepCopy, [str2MutableDeepCopy class], [str2MutableDeepCopy retainCount]);
NSLog(@"str3MutableDeepCopy = %p, %@, %lu", str3MutableDeepCopy, [str3MutableDeepCopy class], [str3MutableDeepCopy retainCount]);
return 0;
}
}
以上程序我们可以得到以下结果:
我们可以看到copy
和stringWithString
得到的结果是一样的,通过这个结果,stringWithString
在一定程度上是可以等价的。在我们使用copy
时,NSString
的类型不会进行改变,由于__NSCFConstantString
,NSTaggedPointerString
无法被释放的,引用计数不会发生变化,但对于__NSCFString
来说就使得其计数器加一。
对于mutableCopy
来说呢,当对于任何对象使用它,最后都只会得到__NSCFString
类型的字符串,引用计数为1,其原因应该在于mutableCopy
对以上三种字符串进行拷贝的时候,创建的都为可变类的容器,所以需要在堆区重新开辟空间进行相应的操作。
总结:
在 Objective-C 中,有三种常见的字符串实现方式:__NSCFConstantString
、__NSCFString
和 NSTaggedPointerString
。
-
__NSCFConstantString
是不可变的字符串常量,它是在编译时创建的,存储在常量区,且在程序生命周期内不可修改。多个相同的字符串常量在内存中只会存储一份。 -
__NSCFString
是不可变的字符串对象,基于动态分配的内存,可以包含任意长度的字符序列。当使用字符串字面量或调用相关的初始化方法创建不可变字符串时,会生成一个__NSCFString
对象。 -
NSTaggedPointerString
是一种优化方式,在64位环境下用于存储小的对象或数字值。它将数据直接存储在指针中,而不需要额外的内存分配。当字符串的长度小于等于9且只包含数字、英文字母等特定字符时,会自动成为NSTaggedPointerString
对象。
在进行复制操作时,可以使用 copy
或 mutableCopy
方法。对于这三种字符串的复制操作:
-
copy
方法会创建一个不可变的副本,对于__NSCFConstantString
和NSTaggedPointerString
类型的字符串,复制后的对象依然是相同类型,引用计数不变。对于__NSCFString
类型的字符串,复制后会创建一个新的对象,并且引用计数加一。 -
mutableCopy
方法会创建一个可变的副本,不论原始字符串是什么类型,最终得到的副本都是__NSCFString
类型的字符串,引用计数为1。
需要注意的是,stringWithString:
方法在一定程度上可以等价于 copy
操作。