NSString 使我们最经常使用的Objective-C的字符串类,但是可能我们对他还不是非常的了解。本篇文章,会记录我在使用NSString时遇到的疑问,以及答疑(本篇文章,是上篇文章测试autoreleasePool所印出来的)
- 先来看一下代码
(声明:使用__weak为了方便测试,不关乎cpu消耗问题)
@implementation ViewController
__weak NSString *weakString1 = nil;
__weak NSString *weakString2 = nil;
__weak NSString *weakString3 = nil;
__weak NSString *weakString4 = nil;
__weak NSString *weakString5 = nil;
- (void)viewDidLoad {
[super viewDidLoad];
NSString *string1 = @"str------------";
NSString *string2 = [NSString stringWithString:@"str------------"];
NSString *string3 = [[NSString alloc] initWithString:@"str------------"];
NSString *string4 = [NSString stringWithFormat:@"str------------"];
NSString *string5 = [[NSString alloc] initWithFormat:@"str------------"];
weakString1 = string1;
weakString2 = string2;
weakString3 = string3;
weakString4 = string4;
weakString5 = string5;
NSLog(@"str1:%@ %p", weakString1, weakString1);
NSLog(@"str2:%@ %p", weakString2, weakString2);
NSLog(@"str3:%@ %p", weakString3, weakString3);
NSLog(@"str4:%@ %p", weakString4, weakString4);
NSLog(@"str5:%@ %p", weakString5, weakString5);
NSLog(@"--------------------------------------");
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"str1:%@ %p", weakString1, weakString1);
NSLog(@"str2:%@ %p", weakString2, weakString2);
NSLog(@"str3:%@ %p", weakString3, weakString3);
NSLog(@"str4:%@ %p", weakString4, weakString4);
NSLog(@"str5:%@ %p", weakString5, weakString5);
NSLog(@"--------------------------------------");
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"str1:%@ %p", weakString1, weakString1);
NSLog(@"str2:%@ %p", weakString2, weakString2);
NSLog(@"str3:%@ %p", weakString3, weakString3);
NSLog(@"str4:%@ %p", weakString4, weakString4);
NSLog(@"str5:%@ %p", weakString5, weakString5);
}
结果
2019-02-18 17:45:36.867279+0800 AutoreleasePool[25715:4092337] str1:str------------ 0x10bf1d078
2019-02-18 17:45:36.867453+0800 AutoreleasePool[25715:4092337] str2:str------------ 0x10bf1d078
2019-02-18 17:45:36.867553+0800 AutoreleasePool[25715:4092337] str3:str------------ 0x10bf1d078
2019-02-18 17:45:36.867681+0800 AutoreleasePool[25715:4092337] str4:str------------ 0x60400005fcb0
2019-02-18 17:45:36.867789+0800 AutoreleasePool[25715:4092337] str5:str------------ 0x60400005f920
2019-02-18 17:45:36.867879+0800 AutoreleasePool[25715:4092337] --------------------------------------
2019-02-18 17:45:36.868021+0800 AutoreleasePool[25715:4092337] str1:str------------ 0x10bf1d078
2019-02-18 17:45:36.868099+0800 AutoreleasePool[25715:4092337] str2:str------------ 0x10bf1d078
2019-02-18 17:45:36.868192+0800 AutoreleasePool[25715:4092337] str3:str------------ 0x10bf1d078
2019-02-18 17:45:36.868287+0800 AutoreleasePool[25715:4092337] str4:str------------ 0x60400005fcb0
2019-02-18 17:45:36.868381+0800 AutoreleasePool[25715:4092337] str5:(null) 0x0
2019-02-18 17:45:36.868461+0800 AutoreleasePool[25715:4092337] --------------------------------------
2019-02-18 17:45:36.879873+0800 AutoreleasePool[25715:4092337] str1:str------------ 0x10bf1d078
2019-02-18 17:45:36.879998+0800 AutoreleasePool[25715:4092337] str2:str------------ 0x10bf1d078
2019-02-18 17:45:36.880261+0800 AutoreleasePool[25715:4092337] str3:str------------ 0x10bf1d078
2019-02-18 17:45:36.880398+0800 AutoreleasePool[25715:4092337] str4:(null) 0x0
2019-02-18 17:45:36.880570+0800 AutoreleasePool[25715:4092337] str5:(null) 0x0
结果是不是和猜想的优点不一样。其实开始的时候我也是有些奇怪,更奇怪的内存释放问题,我们看到1、2、3中情况不仅内存地址一样而且没有释放。而第四种情况则是viewDidAppear中释放,当然这就是跟我们上次说的autoreleasepool有关系,5则是应该和我想的一样。
- 想要理解上边情况,我们需要几方面的知识
关于存储区域
iOS程序分为5大存储区域- 栈区
此区域是系统分配函数参数。局部变量,由系统分配和释放 - 堆区
此区域是程序猿自己分配变量、需要自己释放 - 全局区(也叫做静态区)
这个区域存放全局变量和静态变量,据说已经初始化的全局变量和静态变量放在一块区域,未初始化的在另一块区域 - 文字常量区
存放常量, 字符串常量就是放在这里,此区域内容会程序结束而释放 - 代码区
存放函数体的二进制代码
- 栈区
autoreleasePool
关于autoreleasepool相关的知识,之前的文章已经总结过。传送门
- 解释1.输出结果
由于通过字面量的方式创建的字符串,存放在字符常量区,我们知道此区域的常量特点,不会释放,所以,任何情况下都存在。2、3方式创建方式相当于1方式创建,所以当你写2、3方式创建的时候,编译器会提示Using 'stringWithString:' with a literal is redundant,这种方式完全是多余的。此种方式不会再堆区域,开辟空间,而且此区域,只要常量区的值相等,那么都只会开辟一个内存空间。
关于4、5创建方式,都会在对区域开辟空间,但是他们的创建还有些不同,就是我们所讲的autorelease的问题,关于内存管理的问题,此问题相关传送门已经讲述,就不在这里口舌了。
4.延伸问题
当字符串长度小于等于9和大于9, 有区别吗? 当然有的,我们将1的例子,字符创全部改为@“12345678”,看一下控制台输出:
019-02-18 18:12:57.593888+0800 AutoreleasePool[26062:4111242] str1:12345678 0x10cc28078
2019-02-18 18:12:57.594143+0800 AutoreleasePool[26062:4111242] str2:12345678 0x10cc28078
2019-02-18 18:12:57.594299+0800 AutoreleasePool[26062:4111242] str3:12345678 0x10cc28078
2019-02-18 18:12:57.594464+0800 AutoreleasePool[26062:4111242] str4:12345678 0xa007a87dcaecc2a8
2019-02-18 18:12:57.594582+0800 AutoreleasePool[26062:4111242] str5:12345678 0xa007a87dcaecc2a8
2019-02-18 18:12:57.594711+0800 AutoreleasePool[26062:4111242] --------------------------------------
2019-02-18 18:12:57.594960+0800 AutoreleasePool[26062:4111242] str1:12345678 0x10cc28078
2019-02-18 18:12:57.595088+0800 AutoreleasePool[26062:4111242] str2:12345678 0x10cc28078
2019-02-18 18:12:57.595211+0800 AutoreleasePool[26062:4111242] str3:12345678 0x10cc28078
2019-02-18 18:12:57.595343+0800 AutoreleasePool[26062:4111242] str4:12345678 0xa007a87dcaecc2a8
2019-02-18 18:12:57.595471+0800 AutoreleasePool[26062:4111242] str5:12345678 0xa007a87dcaecc2a8
2019-02-18 18:12:57.595590+0800 AutoreleasePool[26062:4111242] --------------------------------------
2019-02-18 18:12:57.599333+0800 AutoreleasePool[26062:4111242] str1:12345678 0x10cc28078
2019-02-18 18:12:57.599475+0800 AutoreleasePool[26062:4111242] str2:12345678 0x10cc28078
2019-02-18 18:12:57.599589+0800 AutoreleasePool[26062:4111242] str3:12345678 0x10cc28078
2019-02-18 18:12:57.599704+0800 AutoreleasePool[26062:4111242] str4:12345678 0xa007a87dcaecc2a8
2019-02-18 18:12:57.599809+0800 AutoreleasePool[26062:4111242] str5:12345678 0xa007a87dcaecc2a8
什么情况?全部都没有释放?
这是因为什么?
我们采用lldb命令看一下,他们的真实类型
p string1
(__NSCFConstantString *) 5 = 0xa007a87dcaecc2a8 @"12345678"
string1、string2、string3 依然是字符创常量(__NSCFConstantString)和我们上边论述无疑
而string4、string5 却是NSTaggedPointerString, 其实就是taggedPointer对象,直译过来就是标签指针。此类指针对象并不是真正的对象,他没有真正指向的内存,其中用最高位标识是taggedPointer具体对象,最低位标识相关类型或者属性(比如NSNumber标识具体类型,NSString 标识长度)。所以它不会开辟堆空间,内存管理也不由我们管理。
总结
- NSString 初始化不同存储的区域也会不同,生命周期也会不同;
- NSString fomat方式初始化,会根据字符串的长短,优化处理,小于等于9的采用taggedPointer的方式存储,大于9采用将会开辟空间,存储在堆区域;
- 非stringWithFormat方式初始化,用到autorelease的内存管理,大于9的字符串如果想要及时释放,可以采用手动添加@autoreleasepool的方式。
推荐一篇文章,其中详细写了taggedPointer,为什么会出现,解决什么问题,以及相关的知识