你真的懂NSString吗

我们都知道 NSString是一个Objective-C的类,但是我们有时候发现它的对象在内存管理上貌似和其他对象有一些区别。由于这个类是@”Hello Wrod“的基础,所以往往忽略一些细节。让我们看看 NSString 里面一些特性

1. NSString 内存管理特性分析

1.1 准备

为了方便直观的体现差异,定义一个宏,打印 NSString 的 isa、内存地址、值、retainCount。
注:为了解内存特性,此代码用手动内存管理。

#define TLog(_var) ({ NSString *name = @#_var; NSLog(@"%@: %@ -> %p : %@  %d", name, [_var class], _var, _var, (int)[_var retainCount]); })

1.2 NSString 的代码创建

1.2.1 测试 NSString

在OC中,我们一般通过几种方法来创建 NSString 呢,一般有三种方法,现在我们就分别对这三种情况写段测试代码,如下:

NSString *str1 = @"1234567890";
TLog(str1);
// str1: __NSCFConstantString -> 0x10455e018 : 1234567890  -1
NSString *str2 = [NSString stringWithString:@"1234567890"];
TLog(str2);
//str2: __NSCFConstantString -> 0x10455e018 : 1234567890  -1
NSString *str3 = [NSString stringWithFormat:@"1234567890"];
TLog(str3);
// str3: __NSCFString -> 0x600000330fe0 : 1234567890  1
NSString *str4 = [[NSString alloc] initWithString:@"1234567890"];
TLog(str4);
// str4: __NSCFConstantString -> 0x10455e018 : 1234567890  -1
  • 第一、二、四种方式创建出来的 NSString时一模一样的,isa 是 __NSCFConstantString ,内存地址一样,retainCount 是-1。
  • 第三种方式创建的 NSString和创建其他OC对象类似的,在堆上分配内存,初始retainCount为1.

那么问题来了?
1、什么是 __NSCFConstantString
2、为什么会出现 retainCount 为-1?
3、为什么1、2、4这三个 NSString对象内存地址也一样?

1.2.2 NSString创建的写法

其实上面第一种写法和第二种写法是完全一样的,没有任何区别,从 iOS SDK6 开始,第二种写法已经被遗弃了,如果用第二种写法创建 NSString,编译器就会报方法过期的警告。

1.2.3 retainCount为-1是什么情况

首先retainCount是NSUInteger的类型,其实上面的打印是将它作为int类型打印。所以它其实不是-1,它的实际值是4294967295。

在OC的 retainCount 中.如果对象的 retainCount 为这个值,就意味着“无限的 retainCount ”,这个对象是不能被释放的。

所有的 __NSCFConstantString 对象的retainCount都为-1,这就意味着 __NSCFConstantString 不会被释放,使用第一种方法创建的 NSString,如果值一样,无论写多少遍,都是同一个对象。而且这种对象可以直接用 == 来比较。

assert(str1 == str2);      //一直正确
assert(@"abc" == @"abc");  //一直正确

1.3 NSString 的 retain、copy 和 mutableCopy

我们写一段代码分别对 __NSCFConstantString 和 __NSCFString 进行 retain 和 copy 测试

1.3.1 __NSCFConstantString

NSString *string = @"1234567890";
TLog(string);
// string: __NSCFConstantString -> 0x107bb9020 : 1234567890  -1
NSString *retainString = [string retain];
TLog(retainString);
// retainString: __NSCFConstantString -> 0x107bb9020 : 1234567890  -1
NSString *copyString = [string copy];
TLog(copyString);
// copyString: __NSCFConstantString -> 0x107bb9020 : 1234567890  -1
NSString *mutableCopyString = [string mutableCopy];
TLog(mutableCopyString);
// mutableCopyString: __NSCFString -> 0x6000003a3c00 : 1234567890  1

上面的测试可以看出,对一个__NSCFConstantString进行retain和copy操作都还是自己,没有任何变化,对其mutableCopy操作可将其拷贝到堆上,retainCount为1.

1.3.2 __NSCFString

NSString *string = [NSString stringWithFormat:@"1234567890"];
TLog(string);
// string: __NSCFString -> 0x600000a494e0 : 1234567890  1
NSString *retainString = [string retain];
TLog(retainString);
// retainString: __NSCFString -> 0x600000a494e0 : 1234567890  2
NSString *copyString = [string copy];
TLog(copyString);
// copyString: __NSCFString -> 0x600000a494e0 : 1234567890  3
NSString *mutableCopyString = [string mutableCopy];
TLog(mutableCopyString);
// mutableCopyString: __NSCFString -> 0x60000046ab80 : 1234567890  1

上面的测试中,我们发现,对__NSCFString进行retain和mutableCopy操作时,其特性符合正常的对象特性。但是对其copy时,它却变成了一个__NSCFConstantString对象!copy 会使原来的对象引用计数加一,并拷贝对象地址给新的指针。

mutableCopy 不会改变引用计数,会拷贝内容到堆上,生成一个 __NSCFString 对象,新对象的引用计数为1。

1.3.3 __NSTaggedPointerString

NSString *string = [NSString stringWithFormat:@"a"];
TLog(string);
// string: NSTaggedPointerString -> 0xb2590670e6f2b9aa : a  -1
NSString *retainString = [string retain];
TLog(retainString);
// retainString: NSTaggedPointerString -> 0xb2590670e6f2b9aa : a  -1
NSString *copyString = [string copy];
TLog(copyString);
// copyString: NSTaggedPointerString -> 0xb2590670e6f2b9aa : a  -1
NSString *mutableCopyString = [string mutableCopy];
TLog(mutableCopyString);
// mutableCopyString: __NSCFString -> 0x600002647cf0 : a  1

这回我们字符串定义成一个字符长度的的字符串“a”
我们发现怎么类型又变成 NSTaggedPointerString 。而且跟 NSCFConstantString 一样计数器都是-1。

2. 小结

结果是很复杂的,按照产生对象的isa大致可以分为三种情况:
产生的对象是 __NSCFConstantString
产生的对象是 __NSCFString
产生的对象是 __NSTaggedPointerString

__NSCFConstantStringNSTaggedPointerString 计数器为-1,因为 NSString 内部做了某种优化。

类型 引用计数
__NSCFString 1
NSTaggedPointerString、__NSCFConstantString -1

2.1 三种类型分别是什么,分别是在什么情况下产生的,分别处于内存的那个区域?

__NSCFConstantString
字符串常量,是一种编译时常量,它的 retainCount 值很大,是 4294967295,在控制台打印出的数值则是 18446744073709551615==2^64-1,测试证明,即便对其进行 release 操作,retainCount 也不会产生任何变化。是创建之后便是放不掉的对象。相同内容的 __NSCFConstantString 对象的地址相同,也就是说常量字符串对象是一种单例。
这种对象一般通过字面值 @"..."、CFSTR("...") 或者 stringWithString: 方法(需要说明的是,这个方法在 iOS6 SDK 中已经被称为redundant,使用这个方法会产生一条编译器警告。这个方法等同于字面值创建的方法)产生。
这种对象存储在字符串常量区。

__NSCFString
__NSCFConstantString 不同,__NSCFString对象是在运行时创建的一种 NSString子类,他并不是一种字符串常量。所以和其他的对象一样在被创建时获得了 1 的引用计数。
通过 NSString 的 stringWithFormat 等方法创建的 NSString 对象一般都是这种类型。
这种对象被存储在堆上。

__NSTaggedPointerString
理解这个类型,需要明白什么是标签指针,这是苹果在 64 位环境下对 NSString,NSNumber 等对象做的一些优化。简单来讲可以理解为把指针指向的内容直接放在了指针变量的内存地址中,因为在 64 位环境下指针变量的大小达到了 8 位足以容纳一些长度较小的内容。于是使用了标签指针这种方式来优化数据的存储方式。从他的引用计数可以看出,这货也是一个释放不掉的单例常量对象。在运行时根据实际情况创建。

对于 NSString 对象来讲,当非字面值常量的数字,英文字母字符串的长度小于等于 9 的时候会自动成为 NSTaggedPointerString 类型,如果有中文或其他特殊符号(可能是非 ASCII 字符)存在的话则会直接成为 )__NSCFString 类型。
这种对象被直接存储在指针的内容中,可以当作一种伪对象。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355