先看两段代码:
第一段:
@property (nonatomic, strong) NSString *name;
- (void)test1 {
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcde"];
});
}
}
第二段:
@property (nonatomic, strong) NSString *name;
- (void)test2 {
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghjsfdasdfas"];
});
}
}
结论:
第一段代码运行没什么问题;第二段代码运行崩溃,报EXC_BAD_ACCESS,坏内存。
分析:
由于不管是否是在ARC环境下,还是在MRC环境下,底层都是转化成MRC进行编译的。所以对self.name进行赋值相当于下面代码:
- (void)setName:(NSString *)name {
if (_name != name) {
id pre = _name;
// 1.先保留新值
[name retain];
// 2.再进行赋值
_name = name;
// 3.释放旧值
[pre release];
}
}
因为释放旧值是在多条线程同步进行的,所以就会出现当前内存已经被释放了的情况下,接着又被重新释放一次,所以就会导致坏内存的情况出现;但为啥第一段代码不会出错呢?
我们接下来看下两段字符串的对象类型及内存地址:
通过控制台打印的日志,可以看出有一个是TaggedPointer的类型。
接下来可以参考深入理解 Tagged Pointer这篇博客
Tagged Pointer 是一个能够提升性能、节省内存的有趣的技术。
- Tagged Pointer 专门用来存储小的对象,例如 NSNumber 和 NSDate(后来可以存储小字符串)
- Tagged Pointer 指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。
- 它的内存并不存储在堆中,也不需要 malloc 和 free,所以拥有极快的读取和创建速度。