下面的程序會(huì)輸出什么狮鸭?
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]);
輸出結(jié)果
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
在網(wǎng)上搜索了一下合搅,一般人給出的答案是:當(dāng)字符串長(zhǎng)度小于10時(shí),字符串是保存在常量區(qū)歧蕉,沒有引用計(jì)數(shù)灾部。如果長(zhǎng)度大于等于10呢,就會(huì)被復(fù)制到堆去惯退,有引用計(jì)數(shù)赌髓。
后來又出現(xiàn)了一個(gè)詞:Tagged Pointer 具體了解一下。 嘗試著輸出字符串的class催跪,發(fā)現(xiàn)兩者的類名是不同的:
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專門用來存儲(chǔ)小的對(duì)象锁蠕,例如NSNumber和NSDate
Tagged Pointer指針的值不再是地址了,而是真正的值懊蒸。所以荣倾,實(shí)際上它不再是一個(gè)對(duì)象了,它只是一個(gè)披著對(duì)象皮的普通變量而已骑丸。所以舌仍,它的內(nèi)存并不存儲(chǔ)在堆中,也不需要malloc和free通危。
這應(yīng)該也是上面的NSString在長(zhǎng)度小于10的時(shí)候铸豁,沒有引用計(jì)數(shù)的原因了。
引申
這種情況引申出另外一道題:
@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 里面的內(nèi)容改成:
self.strongStr = [NSString stringWithFormat:@"abcdefghijklmn %d", i];
會(huì)如何菊碟?
前者不會(huì)crash, 而后者會(huì)crash节芥。
我們來看一下strongStr的setter方法:
- (void)setStrongStr:(NSString *)strongStr {
if (strongStr == _strongStr) return;
id pre = _strongStr;
[strongStr retain];//1.先保留新值
_strongStr = strongStr;//2.再進(jìn)行賦值
[pre release];//3.釋放舊值
}
結(jié)合上面的Tagged Pointer的解釋,調(diào)用retain or release時(shí)strongStr的引用計(jì)數(shù)一直都是-1;
而對(duì)于后者逆害,strongStr實(shí)際上是一個(gè)對(duì)象头镊,retain會(huì)使引用計(jì)數(shù)+1,release會(huì)使引用計(jì)數(shù) -1;
而對(duì)于多線程異步并行執(zhí)行setStrongStr方法魄幕,可能會(huì)出現(xiàn)這種情況:多個(gè)線程拿到同一個(gè)舊值拧晕,然后給strongStr賦值不同的新值,然后在對(duì)舊值的release時(shí)候梅垄,出現(xiàn)多次release厂捞,程序crash;