iOS Tagged Pointer优化(NSString , NSNumber)

我们可以在WWDC2013的《Session 404 Advanced in Objective-C》视频中,看到苹果对于Tagged Pointer特点的介绍:

  1. Tagged Pointer专门用来存储小的对象,例如NSNumber, NSDate, NSString。
  2. Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。
  3. 在内存读取上有着3倍的效率,创建时比以前快106倍。

既然使用了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类型(即对象类型)

  • NSNumber存储
    地址以0xb开头,转换为二进制为1011,首位1表示这是一个tagged pointer,而011转换为十进制是3,代表对象类型接下来几位,就是以16进制表示的NSNumber的值,而对于最后一位,应该是一个标志位,具体作用,笔者也不是很清楚
    以上是唐神的博客,看着觉得很有道理,但是打印完全不一样,应该是更新了,所以重新摸索一番

字符串继承链

__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);
  1. 在编译期间,就已经决定 NSString -> __NSCFConstantString。所以同一个字符串常量在堆区只分配一个空间,并且 retainCount 为最大。也就是说不会被释放掉。
  • 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

可以看出

  • 创建的可变字符串都是对象类型 __NSCFString(除了直接赋值字面常量是__NSCFConstantString除外,这样写反正会警告,肯定不这样写吧)
  • mutableCopy跟不可变NSString一样,都是新创建对象,然后引用计数为1
  • copy操作就像是直接取了值,创建了一个NSTaggedPointerString类型,引用计数特别大
  1. 在编译期间,就已经决定 NSMutableString -> __NSCFString。所以一个可变字符串常量在堆区会分配一个空间,并且 retainCount 为 1,也就是说按正常对象的生命周期被释放。 可变字符串都是对象类型NSCFString,copy操作后才会是tagPointerString

NSNumber 解析

__NSCFNumber -> NSNumber -> NSValue -> NSObject

NSNumber也是同一个常量就分配一个空间

NSNumber *num4 = @(3.1415927);

当NSNumber为浮点型数据或特别大的数据时,不会按tagPointer存储,是对象类型

总结

在iOS的日常开发中,同样内容的字符串常量 __NSCFConstantString 全局只有一份,放在堆区,并且不会被释放(retainCount值最大)。并且由于有 Tagged Pointer 的存在,尽量避免直接访问对象的 isa 变量。

你可能感兴趣的:(iOS Tagged Pointer优化(NSString , NSNumber))