再探NSString

摘要:
同样,NSStringNSString应该是OC开发中最常用的数据类型。这一次,我们将对这一类型进行全面的探索和总结。NSString本质上是一个OC类对象,继承自NSObject,并符合NSCopying、NSMutableCopying和NSSecureCoding协议。NSMutabbleString与它类似,只是它继承自NSString__ NSCFConstantString类型的字符串常量存储在数据段的常量区域中。通常,此类型可以直接视为NSString类型,存储在堆中。当NSString使用stringWithFormat创建对象时,系统将优先采用标签指针策略来存储对象。当对象的内容和标志大小超过指针大小时,oc对象将以常规方式存储。
再探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的对象占用的内存空间就会加倍。

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

再探NSString第1张

在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存储位置

类名存储位置初始化引用计数
__NSCFString1
NSTaggedPointerString2^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

免责声明:文章转载自《再探NSString》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇vue 格式化时间【网络流#5】UVA 11082 最大流下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

iOS 数据持久化 NSUserDefault

每一个应用都有一个 NSUserDefaults 实例,向 NSUserDefaults 类发送 standardUserDefaults 消息可以得到该实例。 NSUserDefaults 实例类似与 NSMutableDictionary,可以通过键存取或删除该对象。 当应用第一次使用 NSUserDefaults 实例时,NSUserDefaults...

C语言基础:C语言指针(6)

上一节我们讲到了指针和数组, 这次我们来讲解一下指针和字符串, 这次的内容和上一节有相似的地方, 也有全新学习的地方, 让我们一起来看看吧~~下面我们来看一个小例子: #include <stdio.h> int main() { char name[] = "abcde"; name[0] = 'A';...

Rust 智能指针(一)

Rust 智能指针(一) 1.Box<T> Box<T>是指向堆中的指针。 fn main() { let box = Box::new(3); println!("{}", box); } 在出了指针的作用域之后,指针和它指向的对象都将被释放。 在本例中,box将在main函数之后被释放。 由于Box<T&...

delphi之多线程编程(一)

本文的内容取自网络,并重新加以整理,在此留存仅仅是方便自己学习和查阅。所有代码均亲自测试 delphi7下测试有效。图片均为自己制作。 多线程应该是编程工作者的基础技能, 但这个基础我从来没学过,所以仅仅是看上去会一些,明白了2+2的时候,其实我还不知道1+1。 开始本应该是一篇洋洋洒洒的文字, 不过我还是提倡先做起来, 在尝试中去理解. 先试试这个:...

多种方式实现字符串/无符号数反向输出_栈_递归_头尾指针

1、递归调用方式实现无符号数反向输出 C语言实现(DEV c++4.9.9.2运行通过) #include<stdio.h> void reverse_print(unsigned long num) { if(num==0) return; printf("%d",num%10); //输出最低位...

使用lockbits方法处理图像(转)

   许多图像处理任务即时是最简单的文件类型转换,例如从32位深度到8位深度的格式转化,直接获得像素阵列要比使用GetPixel和SetPixel等方法的效率高得多。         你可能会发现DotNet采用托管机制,大多数情况下微软会推荐你使用托管代码,理由是便捷和安全。实际应用中,直接操作内存中的数据块是很少见的,尽管如此,图像处理恰恰是这类为数...