美文网首页
iOS NSString的内存分配

iOS NSString的内存分配

作者: 星星326 | 来源:发表于2023-03-23 17:51 被阅读0次

    面试的时候有时候会随便问一句,判断两个NSString的字面量是否相同,为什么要用isEqualToString来判断,而不能用==来判断呢?
    有些面试者对这个问题可能都没有想过,回答这是一个约定俗成;
    而大多数面试者都会回到:因为==判断的是两个指针是否相等,而NSString是分配到堆上的,每次创建的时候,指针指向的地址的不同的,所以不能用==来判断。

    然而这个结果仍然不能令人满意,或者说只是对了前一半,后面的一半有待商榷。我们知道,oc中我们创建的对象,确实大部分都是分配在堆上的,然而,NSString也是这样么?还是让我们敲敲看吧~

        NSString *test1 = @"123";
        NSString *test2 = @"123";
    
        NSLog(@"%p  %p", test1, test2);
    
        打印结果:
        zbcDemo[36100:5525616] 0x102226df0  0x102226df0
        调试:
        (lldb) p test1==test2
        (bool) $0 = true
    
    

    我们可以看到test1和test2的内存地址是相同的。事实上,@"123"存在于常量存储区也就是_TEXT区,无论你创建、释放多少次,都不会被释放掉。
    如果你有兴趣打印下它的类型和retainCount,可以发现分别是__NSCFConstantString和1152921504606846975。
    事实上,所有的__NSCFConstantString类型的实例都是有无限的retainCount的。这就意味着所有的__NSCFConstantString都不会被释放。

    或者我们换一种写法来创建一个NSString:

    NSString *test1 = [[NSString alloc] initWithString:@"123"];
    NSString *test2 = @"123";
    
    NSLog(@"%p  %p", test1, test2);
    打印结果:
    [36206:5529499] 0x100802df0  0x100802df0
    
    

    这种写法的test1看起来像是新开辟了一块空间,然而我们会发现结果和上面还是一样的。虽然test1的这种写法已经被废弃掉了,但通过打印信息我们其实可以看到test1还是__NSCFConstantString的,和字面量的写法完全没有区别。

    那么回到我们最初的问题,到底为什么NSString要使用isEqualToString呢?
    让我们来试下其他的写法:

        @property (nonatomic, copy) NSString *testStr;
        NSString *test1 = [[NSString alloc] initWithString:@"123"];
        NSString *test2 = @"123";
        NSString *test3 = [NSString stringWithFormat:@"123"];
        NSMutableString *test4 = [[NSMutableString alloc] initWithString:@"123"];
        NSString *test5 = [test4 copy];
        NSString *test6 = [NSString stringWithFormat:@"%@", @"123"];
        self.testStr = [test2 copy];
    
        NSLog(@"%p  %p  %p  %p  %p  %p  %p", test1, test2, test3, test4, test5, test6, self.testStr);
        打印结果:
        zbcDemo[36297:5532377] 0x102ae2df0  0x102ae2df0 
                 0x9a59d631d155838a  0x281287060  0x9a59d631d155838a  
                  0x9a59d631d155838a  0x102ae2df0
    
    

    让我们来猜下他们的内存地址,哪些是相同的呢?答案是test1、test2、self.testStr这三个是相同的,test3、test5、test6这三个是相同的,只有test4没有和它相同的。

    下面简单解释下:
    • self.testStr只是对test2的一个浅拷贝,自然地址和2一样;
    • 3,5,6的类型都是NSTaggedPointerString,4的类型是__NSCFString。3,5,6的字面量虽然和1、2一样的,但是类型其实是不同的。
    • 上面打印的结果中可以看到3,5,6的地址位置非常高,那它们分配在哪个区呢?
    • ** 另外需要注意的是:如果换成较长的字符串,3,5,6的类型也不是NSTaggedPointerString而是__NSCFString**

    要研究明白为什么使用的是不同的类型,首先要清楚什么是NSTaggedPointerString,以及为什么直接用字面量赋值给NSString的时候,苹果不采用NSTaggedPointerString类型。就这个问题,其实【译】采用Tagged Pointer的字符串这里已经讲得很清楚了,这里我再赘述下。

    比如如下代码:

    NSString *a = @"a";
    NSString *b = [[a mutableCopy] copy];
    NSLog(@"%p %p", a, b);
    
    

    运行后可以非常明显的看到:a是一个__NSCFConstantString,b是一个NSTaggedPointerString,自然有不同的内存地址。为什么字面量常量苹果不使用NSTaggedPointerString呢?

    【译】采用Tagged Pointer的字符串中文版的 翻译有些晦涩,看了下英文版的描述比较易懂些:

    although a string like @"a" could be stored as a tagged pointer, constant strings are never tagged pointers. Constant strings must remain binary compatible across OS releases, but the internal details of tagged pointers are not guaranteed.

    原因是常量字符串需要在跨系统上保持二进制兼容,而 tagged pointers在技术上并不能保证这个。因此对于这种短的字符串字面量还是使用\ __NSCFConstantString类型。

    下面一个问题,tagged pointers在内存上分配在哪个区?

    点击查看图片

    其实如果我们仔细在XCode中多点两下,就可以看到其实tagged pointers是没有isa指针的,说明它根本不是一个对象。究其原因这个要说到tagged pointers是为什么被创造出来。

    一般来说,对象所占内存是和CPU位数相关的。在32位的时候,比如一个NSNumber对象占用的空间是4(对象指针)+4(对象的值)=8字节,升级到64位的时候,逻辑不变的话,占用的空间直接翻倍,变成8+8=16字节,这样会产生十分严重的效率问题:为了存储和访问一个NSNumber对象,我们需要在堆上为其分配内存,另外还要维护它的引用计数,管理它的生命期。这些都给程序增加了额外的逻辑,造成运行效率上的损失。

    在查找资料的过程中也发现了苹果官方的明确说法(摘自深入理解Tagged Pointer):

    我们也可以在WWDC2013的《Session 404 Advanced in Objective-C》视频中,看到苹果对于Tagged Pointer特点的介绍:

    • Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate
    • Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。
    • 在内存读取上有着3倍的效率,创建时比以前快106倍。

    由此看来,NSTaggedPointerString根本不是对象,是分配在栈区的。

    本来只是想说下为什么不用==的,不知不觉深究了这么多,对自己理解也是个梳理和提升,屡清了之前很多的盲点。现在总算是明白了,NSString真的是很复杂的东西,可能分配在栈区、堆区、常量区,虽然日常中我们基本上可以无视这些区别,看似没有什么用,然而对自己来说,对做技术来说,多较些真,这样才能走的更远吧。

    所以,回到最初的问题,我们需要怎么回答,如何判断两个字符串是否相等呢 (=@__@=)
    相关文章:
    深入理解Tagged Pointer
    【译】采用Tagged Pointer的字符串

    相关文章

      网友评论

          本文标题:iOS NSString的内存分配

          本文链接:https://www.haomeiwen.com/subject/oyiirdtx.html