美文网首页iOS面试iOS面试题iOS高级开发面试题整理
iOS 开发刷题系列三:NSString 引用计数

iOS 开发刷题系列三:NSString 引用计数

作者: 34码的小孩子 | 来源:发表于2018-07-03 15:21 被阅读44次

    下面的程序会输出什么?

        NSMutableArray *ary = [[NSMutableArray array] retain];
        NSString *str = [NSString stringWithFormat:@"123456789"];
        NSString *longStr = [NSString stringWithFormat:@"1234567890"];
        
        [str retain];
        [longStr retain];
        [ary addObject:str];
        [ary addObject:longStr];
        
        NSLog(@"str = %ld", (unsigned long)[str retainCount]);
        NSLog(@"longStr = %ld", (unsigned long)[longStr retainCount]);
        
        [str retain];
        [str release];
        [str release];
        [longStr retain];
        [longStr release];
        [longStr release];
        
        NSLog(@"str = %ld", (unsigned long)[str retainCount]);
        NSLog(@"longStr = %ld", (unsigned long)[longStr retainCount]);
        [ary removeAllObjects];
        NSLog(@"str = %ld", (unsigned long)[str retainCount]);
        NSLog(@"longStr = %ld", (unsigned long)[longStr retainCount]);
    

    输出结果

    2018-07-03 13:54:59.951143+0800 BlockTestDemo[13502:2107264] str = -1
    2018-07-03 13:54:59.951374+0800 BlockTestDemo[13502:2107264] longStr = 3
    2018-07-03 13:54:59.951613+0800 BlockTestDemo[13502:2107264] str = -1
    2018-07-03 13:54:59.951717+0800 BlockTestDemo[13502:2107264] longStr = 2
    2018-07-03 13:54:59.951956+0800 BlockTestDemo[13502:2107264] str = -1
    2018-07-03 13:54:59.952044+0800 BlockTestDemo[13502:2107264] longStr = 1
    

    在网上搜索了一下,一般人给出的答案是:当字符串长度小于10时,字符串是保存在常量区,没有引用计数。如果长度大于等于10呢,就会被复制到堆去,有引用计数。

    后来又出现了一个词:Tagged Pointer 具体了解一下。 尝试着输出字符串的class,发现两者的类名是不同的:

    NSString *str = [NSString stringWithFormat:@"123456789"];
    NSString *longStr = [NSString stringWithFormat:@"1234567890"];
    NSLog(@"str %s %p", object_getClassName(str), str);
    NSLog(@"longStr %s %p", object_getClassName(longStr), longStr);
    
    2018-07-03 13:54:59.950804+0800 BlockTestDemo[13502:2107264] str NSTaggedPointerString 0xa1ea1f72bb30ab19
    2018-07-03 13:54:59.950979+0800 BlockTestDemo[13502:2107264] longStr __NSCFString 0x60c000224f20
    

    Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate
    Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。

    这应该也是上面的NSString在长度小于10的时候,没有引用计数的原因了。

    引申

    这种情况引申出另外一道题:

    @property (nonatomic, strong) NSString *strongStr;
    
    dispatch_queue_t queue = dispatch_queue_create("strongStr", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0; i < 100000; i++) {
            dispatch_async(queue, ^{
                self.strongStr = [NSString stringWithFormat:@"ab %d", i];
            });
        }
    

    如果将dispatch_async 里面的内容改成:

    self.strongStr = [NSString stringWithFormat:@"abcdefghijklmn %d", i];
    

    会如何?
    前者不会crash, 而后者会crash。
    我们来看一下strongStr的setter方法:

    - (void)setStrongStr:(NSString *)strongStr {
        if (strongStr == _strongStr) return;
        id pre = _strongStr;
        [strongStr retain];//1.先保留新值
        _strongStr = strongStr;//2.再进行赋值
        [pre release];//3.释放旧值
    }
    

    结合上面的Tagged Pointer的解释,调用retain or release时strongStr的引用计数一直都是-1;
    而对于后者,strongStr实际上是一个对象,retain会使引用计数+1,release会使引用计数 -1;
    而对于多线程异步并行执行setStrongStr方法,可能会出现这种情况:多个线程拿到同一个旧值,然后给strongStr赋值不同的新值,然后在对旧值的release时候,出现多次release,程序crash;

    相关文章

      网友评论

        本文标题:iOS 开发刷题系列三:NSString 引用计数

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