@property (nonatomic, strong) NSString *target;
//....
dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
dispatch_async(queue, ^{
self.target = [NSString stringWithFormat:@"ksddkjalkjd%d",i];
});
}
错误现象
这个错误是不是经常看到,EXC_BAD_ACCESS错误,话不多说一顿想,先自己估计什么原因。野指针,或者过度释放,我的初步想法。
崩溃堆栈,停在了0 objc_release上面,说明给空对象发送了release消息。大概结果就是过度释放。那么过度释放在哪儿呢?
上面就是最普通的一个自定义队列中循环创建异步队列,那么原因只可能出现在这个
self.target = [NSString stringWithFormat:@"ksddkjalkjd%d",i];
看上面的target属性是nonatomic,strong,答案呼之欲出了。
产生原因
strong属性的生成方法是:
- (void)setTarget:(NSString *)target { [target retain]; //先保留新值 第一步 [_target release];//再释放旧值 第二步 _target = target; //再进行赋值 第三步 }
因为是自定义队列,相当于是并行队列,并行队列中的异步任务,执行完成时间是随机的,那么就可能出现队列A,执行到了第二步,队列B也执行到了第二步,队列A的`_target = target;`还没执行到,也就是_target被过度release,导致了向空对象发送消息导致崩溃发生
解决对策
1.使用串行队列
将set方法改成在串行队列中执行就行,这样即使异步,但所有block操作追加在队列最后依次执行。
2. 使用atomic atomic关键字相当于在setter方法加锁,这样每次执行setter都是线程安全的,但这只是单独针对setter方法而言的狭义的线程安全。 3.使用weak关键字 weak的setter没有保留新值或者保留旧值的操作,所以不会引发重复释放。当然这个时候要看具体情况能否使用weak,可能值并不是所需要的值。 4.使用Tagged Pointer Tagged Pointer是苹果在64位系统引入的内存技术。简单来说就是对于NSString(内存小于60位的字符串)或NSNumber(小于2^31),64位的指针有8个字节,完全可以直接用这个空间来直接表示值,这样的话其实会将NSString和NSNumber对象由一个指针转换成一个值类型,而值类型的setter和getter又是原子的,从而线程安全。比如上述代码的字符串改短一些,就不会崩溃了。
总结经验
从而我们可以总结到,线程安全有以下几种方法: 1、单线程串行访问 2、访问加锁 3、使用不进行额外操作的关键字(weak) 4、使用值类型
转载自 http://www.cocoachina.com/ios/20170825/20376.html
如有侵权请联系,即刻删除
2017年9月14日更新:
各位看我博客的另外一篇文章,有关这篇博客的解析。
直接说结论,不是target的release问题,而是for循环i值的问题。
http://blog.csdn.net/wsgtc8080/article/details/77980331