怪异的exc_bad_access崩溃与iOS中的setter/getter相关



有人可以解释为什么我的代码崩溃之后吗?崩溃发生在FOO方法中的块内。我有exc_bad_access或"对象错误:双免费"。我还得到了" - [nsobject Description]:发送到Deallocated实例的消息"启用Zombie Objects"。

@interface ViewController ()
@property (nonatomic, strong) NSObject *obj;
@end
@implementation ViewController
// just adding button
- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
    [btn setTitle:@"test" forState:UIControlStateNormal];
    btn.frame = CGRectMake(100, 100, 100, 100);
    [btn addTarget:self action:@selector(btnAction:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];
}
// fired by button
- (void)btnAction:(id)sender {
    for (int i = 0; i < 100; i++) {
        [self foo];
    }
}
// I want to understand this method
- (void)foo {
    NSLog(@"foo");
    self.obj = NSObject.new;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"%@", [[self obj] description]);  // sometimes crash happenes here with a message "-[NSObject description]: message sent to deallocated instance"
    });
}
@end

看起来[self obj]和[obj描述]之间是self.obj。但是我不确定为什么。

我认为,来自[self obj]的对象应由其范围所有,即使self.obj = nsobject.new在其他线程上同时执行。我的理解错了吗?

我正在使用ARC测试iOS 7.0.4。谢谢!

您有一个正在调用 -foo方法的循环,因此self.obj迅速被设置为新值。每次发生这种情况时,您都会访问您(nonatomic)属性的代码。但是,即使从多个线程访问该属性时始终为该属性获得正确的值,在使用该属性的先前值完成背景线程完成之前,主线程很可能将属性设置为新值。一旦属性更改为新值,它就会释放到它分配给它的先前对象。

由于您是从多个线程访问属性的,因此您希望它是原子,而不是非原子,因此请将您的属性更改为:

@property (strong) NSObject *obj;

atomic是默认值。使用异步块进行以下操作也可能更安全:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSObject *obj = self.obj;
    if (self.obj) {
        NSLog(@"%@", [obj description]);
    }
});

如果您这样做,您不应该再看到崩溃了,因为obj始终是nil或有效的对象,在块内有很强的引用。

但是,您可能不会从中获得期望的结果。对于每次执行异步块,不能保证您将获得您要创建的NSObject的随后实例。有时,它可能会执行您的块,其中obj两次都是同一对象,而您从未看到创建的某些对象。这是因为您的异步块在打电话调用块之前没有立即设置实例,它是从属性中获取的。如果您希望此之前立即使用实例集,则必须执行以下操作:

__block NSObject *obj = NSObject.new;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"%@", [obj description]);
});

这应该始终使用您专门为异步块的调用而创建的实例。

我怀疑问题是由 nonatomic属性属性引起的,因为您正在重新分配self.obj 100次我认为有可能背景线程读取部分重新分配的对象指针。p>请尝试:

@property (atomic, strong) NSObject *obj;

在进行背景记录时, self.obj可能是不同的或在更改的中间。

使用这样的本地变量:

- (void)foo {
    NSLog(@"foo");
    NSObject *val = [NSObject new];
    self.obj = val;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"%@", val);
    });
}

这将避免线程问题并确保NSLog记录适当的实例。