先解决个问题
首先定义
@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指针上做手脚。在指针上存放具体值。
TaggedPointer
64位开始 引入了Tagged Pointer 技术,用于优化NSNumber、NSDate、NSString 等小对象存储
打印方式一、方式二的NSString地址
从上图可以看出 0结尾的为对象地址 因为以16位为基准 内存对齐
而方法二的明显不一样。
我们看一下objc_release的源码
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;
}
#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__ //如果是OSX && X86
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else //其他情况 包含iOS
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1
#endif
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63) //如果是OSX && X86
#else
# define _OBJC_TAG_MASK 1UL //其他情况 包含iOS
#endif
从上面可以看当在OSX && X86 出当1UL<<63为1的时候为TaggedPointer
从上面可以看当在iOS平台 出当尾数为1的时候为TaggedPointer
感兴趣的可以关注我的公众号。每天会更新哦 非常感谢。相互交流 提升技术~