我们可以在WWDC2013的《Session 404 Advanced in Objective-C》视频中,看到苹果对于Tagged Pointer特点的介绍:
既然使用了tagged pointer,那么就失去了iOS对象的数据结构,但是,系统还是需要有个标志位表明当前的tagged pointer 表示的是什么类型的对象。这个标志位,也是在最高4位来表示的。
我们将0xa转换为二进制,得到
1010,其中最高位1xxx表明这是一个tagged pointer,而剩下的3位010,表示了这是一个NSString类型。010转换为十进制即为2。也就是说,标志位是2的tagger pointer表示这是一个NSString对象。
在字符串长度在9个以内时,iOS其实使用了tagged pointer做了优化的, 直到字符串长度大于9,字符串才真正成为了__NSCFString类型(即对象类型)
__NSCFConstantString -> __NSCFString -> NSMutableString -> NSString -> NSObject
NSString *str = @"abc"; // __NSCFConstantString
NSString *str1 = @"abc"; //__NSCFConstantString
NSString *str2 = [NSString stringWithFormat:@"%@", str]; // NSTaggedPointerString
NSString *str3 = [str copy]; // __NSCFConstantString
NSString *str4 = [str mutableCopy]; // __NSCFString
NSLog(@"str(%@<%p>: %p): %@", [str class], &str, str, str);
NSLog(@"str1(%@<%p>: %p): %@", [str1 class], &str1, str1, str1);
NSLog(@"str2(%@<%p>: %p): %@", [str2 class], &str2, str2, str2);
NSLog(@"str3(%@<%p>: %p): %@", [str3 class], &str3, str3, str3);
NSLog(@"str4(%@<%p>: %p): %@", [str4 class], &str4, str4, str4);
NSCFConstantString这种对象一般通过字面值 @"…"、CFSTR("…") 或者 stringWithString: 方法产生。
NSTaggedPointerString
理解这个类型,需要明白什么是标签指针,这是苹果在 64 位环境下对 NSString,NSNumber 等对象做的一些优化。简单来讲可以理解为把指针指向的内容直接放在了指针变量的内存地址中,因为在 64 位环境下指针变量的大小达到了 8 位足以容纳一些长度较小的内容。于是使用了标签指针这种方式来优化数据的存储方式。从他的引用计数可以看出,这货也是一个释放不掉的单例常量对象。在运行时根据实际情况创建。
对于 NSString 对象来讲,当非字面值常量的数字,英文字母字符串的长度小于等于 9 的时候会自动成为 NSTaggedPointerString 类型,如果有中文或其他特殊符号(可能是非 ASCII 字符)存在的话则会直接成为 )__NSCFString 类型。
这种对象被直接存储在指针的内容中,可以当作一种伪对象。
__NSCFString
和 __NSCFConstantString 不同, __NSCFString 对象是在运行时创建的一种 NSString 子类,他并不是一种字符串常量。所以和其他的对象一样在被创建时获得了 1 的引用计数。
通过 NSString 的 stringWithFormat 等方法创建的 NSString 对象一般都是这种类型。
这种对象被存储在堆上。
copy与mutableCopy
NSString *str1 = @"sa";
TLog(str1);
//str1: __NSCFConstantString -> 0x100001050 : sa 18446744073709551615
NSString *str2 = @"sa";
TLog(str2);
//str2: __NSCFConstantString -> 0x100001050 : sa 18446744073709551615
NSString *str3 = @"1234567890";
TLog(str3);
//str3: __NSCFConstantString -> 0x100001110 : 1234567890 18446744073709551615
NSString *str4 = [NSString stringWithFormat:@"sa"];
TLog(str4);
//str4: NSTaggedPointerString -> 0x617325 : sa 18446744073709551615
NSString *str5 = [NSString stringWithFormat:@"sa"];
TLog(str5);
//str5: NSTaggedPointerString -> 0x617325 : sa 18446744073709551615
NSString *str6 = [NSString stringWithFormat:@"1强"];
TLog(str6);
//str6: NSTaggedPointerString -> 0x1ea1f72bb30ab195 : 123456789 18446744073709551615
NSString *str7 = [NSString stringWithFormat:@"1234567890"];
TLog(str7);
//str7: __NSCFString -> 0x100300800 : 1234567890 1
//copy操作
NSString *str8 = [str6 copy];
TLog(str8);
//__NSCFString -> 0x600001077540 : 1强 3
NSString *str8 = [str5 copy];
TLog(str8);
//str8: NSTaggedPointerString -> 0xb1d8c1c416284ebd : sa 9223372036854775807
NSString *str8 = [str5 mutableCopy];
TLog(str8);
//str8: __NSCFString -> 0x600001e14150 : sa 1
通过输出结果可以看出,mutableCopy对于任何的String类型 不会改变引用计数,会拷贝内容到堆上,生成一个 __NSCFString 对象,新对象的引用计数为1.
而copy只有对于NSCFString对象类型引用计数才会加一,其他类型的话就指针还是指向被复制的地址
NSMutableString *mutableString1 = [NSMutableString stringWithFormat:@"fsfsf"];
TLog(mutableString1);
//mutableString1: __NSCFString -> 0x600000097450 : fsfsf 2
NSMutableString *mutableString2 = @"12345";
TLog(mutableString2);
//mutableString2: __NSCFConstantString -> 0x1060a7230 : 12345 1152921504606846975
NSMutableString *mutableString2 = [NSMutableString stringWithString:@"12345"];
TLog(mutableString2);
//mutableString2: __NSCFString -> 0x600000617900 : 12345 1
NSString *str10 = [mutableString1 copy];
TLog(str10);
//str10: NSTaggedPointerString -> 0x9bb5473671a403ec : fsfsf 9223372036854775807
NSString *str11 = [mutableString2 mutableCopy];
TLog(str11);
//str11: __NSCFString -> 0x6000000971e0 : 12345 1
可以看出
__NSCFNumber -> NSNumber -> NSValue -> NSObject
NSNumber也是同一个常量就分配一个空间
NSNumber *num4 = @(3.1415927);
当NSNumber为浮点型数据或特别大的数据时,不会按tagPointer存储,是对象类型
在iOS的日常开发中,同样内容的字符串常量 __NSCFConstantString 全局只有一份,放在堆区,并且不会被释放(retainCount值最大)。并且由于有 Tagged Pointer 的存在,尽量避免直接访问对象的 isa 变量。