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

tai*_*ino 0 memory-management objective-c ios automatic-ref-counting

有人可以解释为什么跟随我的代码崩溃吗?在foo方法中的块内发生崩溃。我有EXC_BAD_ACCESS或“对象错误:双重释放”。当我将“启用僵尸对象”设置为“开”时,我还收到了“-[NSObject description]:消息发送到已释放实例”。

@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
Run Code Online (Sandbox Code Playgroud)

看起来self.obj被释放在[self obj]和[obj description]之间。但是我不确定为什么。

我认为[self obj]中的对象应该属于它的作用域,即使在其他线程上同时执行self.obj = NSObject.new也不应该将其释放。我的理解错了吗?

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

Gav*_*vin 5

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

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

@property (strong) NSObject *obj;
Run Code Online (Sandbox Code Playgroud)

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

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

如果执行此操作,您将不再看到崩溃,因为在块内obj它将始终是nil或有效引用了该对象的有效对象。

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

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

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