奇怪的EXC_BAD_ACCESS崩溃与iOS中的setter / getter有关

时间:2014-02-03 16:11:11

标签: ios objective-c memory-management automatic-ref-counting

有人可以解释为什么我的代码会崩溃吗?在foo方法中块内发生崩溃。 我有EXC_BAD_ACCESS或“对象错误:双倍免费”。当我设置“启用僵尸对象”时,我也得到了“ - [NSObject描述]:消息发送到解除分配的实例”。

@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在[self obj]和[obj description]之间被释放。但我不确定为什么。

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

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

3 个答案:

答案 0 :(得分:5)

你有一个调用你的-foo方法的for循环,所以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]);
});

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

答案 1 :(得分:0)

我怀疑问题是由nonatomic属性属性引起的,因为你重新分配self.obj 100次我认为后台线程有可能读取部分重新分配的对象指针。

请尝试:

@property (atomic, strong) NSObject *obj;

答案 2 :(得分:0)

到后台日志记录完成时,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记录正确的实例。