再探NSString

再探NSString

NSString应该是oc开发中最常用的一个数据类型了,这次对该类型再进行一次全方位的探索与总结。

NSString本质上属于OC类对象,继承于NSObject,遵守NSCopying, NSMutableCopying, NSSecureCoding协议。

NSMutableString与之类似,唯一不同的是它继承于NSString。

通过语法糖创建NSString

大部分的开发中,我们都会使用@+双引号的方式创建NSString对象。如下:

NSString* str=@"i am a single str";

这种创建方式创建出来的类型是什么呢?它存储的空间地址又在哪里呢?不妨打印一下:

NSLog(@"%@:%p",[str class],str);
//打印如下:
//__NSCFConstantString:0x100002058

为什么明明是NSString类型的在这里会变成__NSCFConstantString类型,这是因为NSString其实是个类族(大部分容器类也是如此),它的初始化方法返回的实例对象其实是隐藏在类族中的公共接口后面的某一内部类型。

类族(class cluster):属于一种设计模式,用以隐藏“抽象基类”背后的实现细节。oc的系统框架中普遍使用此模式。

其实__NSCFConstantString类型是一种字符串的常量类型,当切换成MRC模式下使用retainCount会发现它的引用计数会非常大,通常为:2^64-1,这样设置的原因就是无论对其进行多次release都能够保证对象不会被释放,所以可以直接将其看作为一个单例。对于单例对象,那么只要有相同的内容,他们的内存地址也就相同。如下所示:

NSString* str0=@"i am not a single str";
NSString* str1=@"i am not a single str";
NSLog(@"%@:%p",[str0 class],str0);
NSLog(@"%@:%p",[str1 class],str1);

//打印如下:
//__NSCFConstantString:0x100002050
//__NSCFConstantString:0x100002050

这些字符串对象都存储于字符串常量区。

OC有五大内存管理区域,地址由小到大分别为:代码段、数据段、BSS段、堆、栈。

__NSCFConstantString类型的字符串常量就存储于数据段当中的常量区。

通过stringWithFormat创建NSString

通常在开发中,需要将得到的临时变量或者类对象的字符串进行拼接时会用到这个类方法class method。

那么这种创建方式创建出来的类型是什么呢?它存储的空间地址又在哪里呢?不妨再次打印一下:

NSString* str=[NSString stringWithFormat:@"hi"];
NSLog(@"%@:%p",[str class],str);

//打印如下:
NSTaggedPointerString:0x723673ffe0bc6c91

说到NSTaggedPointerString这个类型,就要说到标签指针(tagged pointer)了,苹果在推出64位架构的A7双核处理器,也就是5s的时候,为了节省内存和提高执行效率,苹果提出了Tagged Pointer的概念来标注特定类型的数值。

标签指针:标签指针在不使用原来类型对象的情况下,把与数值有关的全部消息都放在指针值里面。运行期系统会在消息派发期间检测到这种标签指针。除了NSString外,NSNumber、NSDate类型也会采用该策略。

那么为什么原有对象会浪费内存?我们可以拿NSNumber对象来举个例子。众所周知,NSNumber的占位与CPU的位数有关。在32位机器上整数会占4个字节,64位上则占8个字节。所以直接采取原有对象类型来存储,从32位机迁移到64位机后,NSNumber的对象占用的内存空间就会加倍。

具体存储策略如下图所示:

在64位机器上,我们可以将采用标签指针策略的对象看成由两个部分的指针组成。第一个部分用来存储特殊标记,表示这是一个采用了标签指针策略的指针(不指向任何内存地址),第二个部分剩余的内存大小都可以用作存储数据。

但是当字符串内容超过了指针范围后,就不会再采取标签指针的策略了。例如初始化一个很长的德语单词:

NSString* str=[NSString stringWithFormat:@"Kraftfahrzeughaftpflichtversicherung"];
NSLog(@"%@->%@->%@->%@:%p",[str class],[[str class] superclass],[[[str class] superclass] superclass],[[[[str class] superclass] superclass] superclass],str);

//打印如下:
__NSCFString->NSMutableString->NSString->NSObject:0x103825090

由此可知,__NSCFString继承于NSMutableString,而NSMutableString又继承于NSString。之所以会出现这种状况也就是因为oc的系统框架中使用了类族的模式。一般情况下可以把该类型直接看做为NSString类型,该对象存储于堆中。

不同类型的NSString存储位置

类名 存储位置 初始化引用计数
__NSCFString 1
NSTaggedPointerString 2^64-1
__NSCFConstantString 常量区 2^64-1

总结

  • 采用语法糖创建NSString会以单例模式存储于常量区。
  • 当NSString采用stringWithFormat来创建对象时,系统会优先采取标签指针策略来存储对象,当对象的内容和Flag大小超出了指针大小,则采用常规方式存储oc对象。
  • 无论采用什么方式初始化NSString,它的类型都不会是NSString类型,因为系统生成NSString的接口都是隐藏在类族中的公共接口。所以一般不会使用 [str class]==[NSString class] [str isMemberOfClass:[NSString class]]来判断str类型,而是采用 [str isKindOfClass:[NSString class]]来判断。

参考文章:
1.NSTaggedPointerString
2.MemoryAllocation

posted @ 2020-05-20 14:03  Solist  阅读(267)  评论(0编辑  收藏  举报