NSString 遇到的坑

1,795 阅读1分钟

参考文章

小谈NSString的内存分配

ObjC中怎么判断可变和不可变的坑

我遇到的问题

知识补充:以字面量形式或者 initWithString: 创建出来的字符串是在常量区的,不会释放。以 stringWithFormat: 创建出来的是在堆上。

在这个截图中可以看出来的问题:

  1. string1 与 string3 是 __NSCFConstantString 类型的,它们是在常量区的。
  2. string4 是 __NSCFString 类型的,它的地址比 string1 与 string3 高的多,它是在堆上的。
  3. string2 是 NSTaggedPointerString 类型的。它的地址的第一位位是 a (十六进制),如果在二进制下,这个地址的首位为 1。地址首位为 1,在 iOS 平台下,就是 TaggedPointer 处理过的(看源码可以知道)。

问题一

@property (nonatomic, copy) NSString * name;
for (int i = 0; i < 1000; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.name = [NSString stringWithFormat:@"abcdefghij"];
        });
}
for (int i = 0; i < 1000; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.name = [NSString stringWithFormat:@"abcdefghi"];
        });
}

第一段代码会崩溃,因为在 - setName: 方法中多次释放 _name。
第二段代码不会崩溃,它与第一个不同的是,这个字符串比较短,采用的是 taggedpointer 技术。 它为什么不崩溃,暂时不太理解?我打上断点发现每次都会进入- setName: 方法,那么每次进来都会有 release 才对。

问题二

for (int i = 0; i < 1000; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.name = @"abcdefghij";
        });
}

这段代码是使用的比更长的那个字符串。
这段代码也不会崩溃。
它是创建在常量区的。
它为什么不崩溃,我也不知道。

问题三

NSString *string = [NSString stringWithFormat:@"abcdefghij"];
for (int i = 0; i < 1000; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        self.name = string;
    });
}

这段代码也是使用的比较长的那个字符串。在第一段代码中,它崩了,在这段里面,它没崩。为什么?