@property (nonatomic, copy) NSString *test;
for (int i = 0; i < 1000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.test = [NSString stringWithFormat:@"%@",@"123"];
});
}
for (int i = 0; i < 1000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.test = [NSString stringWithFormat:@"%@",@"abababababababababababababab"];
});
}
运行段代码 有什么区别?现象是什么?
查看崩溃日志
test属性 setter方法实际执行以下内容
- (void)setTest:(NSString *)test {
if (![_test isEqualToString:test]) {
[_test release];
_test = [test copy];
[test release];
}
}
由于test 修饰为nonatomic 所以是线程不安全的。当多条线程同时访问,造成多次release ,所以坏内存访问。
修饰改为atomic 或者加锁
首先打印两个NSString的类型
正常对象都是 指针指向对象的地址, 指针指向堆内存中的地址,所以方法二会因为多线程访问而造成坏内存访问,而TaggedPointer 则不会创建内存,而是在isa指针上做手脚。在指针上存放具体值。
64位开始 引入了Tagged Pointer 技术,用于优化NSNumber、NSDate、NSString 等小对象存储
从上图可以看出 0结尾的为对象地址 因为以16位为基准 内存对齐
而方法二的明显不一样。
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
当obj为isTaggedPointer的时候 直接返回。所以更加验证了刚才的说法 即:用指针存值,而不是在堆中生成对象
objc_object::isTaggedPointer()
{
return _objc_isTaggedPointer(this);
}
# define _OBJC_TAG_MASK 1UL
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
从上面可以看出当尾数为1的时候为TaggedPointer
偶尔会出现尾数为不为1的TaggedPointer 不知道什么原因。但源码确实是这么写的。