【问题标题】:Weird EXC_BAD_ACCESS crash related with setter/getter in iOS与 iOS 中的 setter/getter 相关的奇怪的 EXC_BAD_ACCESS 崩溃
【发布时间】:2014-02-27 05:21:05
【问题描述】:

有人可以解释为什么我的代码崩溃了吗?崩溃发生在 foo 方法的块内。 我有 EXC_BAD_ACCESS 或“对象错误:双重释放”。当我将“启用僵尸对象”设置为 ON 时,我还收到了“-[NSObject description]: message sent to deallocated instance”。

@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 上进行测试。谢谢!

【问题讨论】:

  • 这是你的真实代码吗? [self obj] description] 看起来不会编译。
  • 你为什么要在后台登录?
  • @AaronBrager 很抱歉这是我的错误。更新帖子!
  • @rmaddy 没有任何意义。我的真实代码更复杂。我只是尝试制作最简单的示例代码来解决这个问题。

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


【解决方案1】:

您有一个 for 循环正在调用您的 -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]);
});

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

【讨论】:

  • 感谢您的回复。是的,我知道原子解决了这个问题。但我仍然不明白为什么我的代码会崩溃。这只是重现我真正问题的示例代码。所以我不期望在 self.obj = NSObject.new; 中使用相同的 obj;和 [[self obj] 描述]。但是我希望在 [self obj] 和 [obj description] 之间应该使用相同的对象,这是错误的吗?
  • 这个问题很可能发生,因为在使用[self obj] 获取对象和对其调用-description 方法之间,由于属性被设置为新值,它被解除分配。事实上,谁知道你会得到什么,因为该属性是 nonatomic 并且可以从多个线程访问。当您从属性中获取值时,它可能已经被释放,并且您获得的是旧值,因为您再次从多个线程同时访问 nonatomic 属性。
  • 你是说所有非原子属性都可以返回释放的对象吗?我在 opensource.apple.com/source/objc4/objc4-532.2/runtime/… 中看到了真正的SetProperty 方法。但我看不出有任何理由哪个非原子属性会在其中返回已释放的对象。
  • 可能不会返回已释放的对象,但返回的对象可能会在返回后很快被释放。看看那里的objc_getProperty_non_gc 的实现,看看如果它不是原子的,它会做什么。当它不是原子时,返回值不会被保留和自动释放,这意味着它可以随时被释放。
  • 不,再看一下方法,返回发生在这个点:if (!atomic) return *slot;,而不是在自动释放点。所以它不会被保留和自动释放,它只是被返回。这就是为什么它不是线程安全的,因为不同的线程可能会执行一些可能导致它在您有机会保留或使用它之前被释放的事情。这就是为什么如果要从多个线程访问它,它需要是原子的。
【解决方案2】:

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

【讨论】:

  • 你缺少了 *,你需要将 val 作为指向 NSObject 的指针。此外,该块将复制val,因为您没有使用__block 关键字。您会在我的回答中看到该实现。
  • 修正了错字 - 谢谢。这整个问题及其代码似乎非常做作。
  • 我同意,这不是某人真正会做的事情。但如果他试图对此进行扩展,在 100 个不同的对象上做一些事情,他应该知道他的代码有这个缺陷。
  • @rmaddy 感谢您的回复!我了解您的代码更好。但我想知道为什么会发生“self.obj 可能不同或正在更改中”。根据此代码opensource.apple.com/source/objc4/objc4-532.2/runtime/… 中的reallySetProperty,将新值分配给ivar 后释放旧值。所以如果 new val 在 [self obj] 之前赋值就可以了。而且,如果在 [self obj] 之后分配了新 val,我认为旧值属于它的范围,不应该被释放。这是错的吗?
【解决方案3】:

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

请尝试:

@property (atomic, strong) NSObject *obj;

【讨论】:

  • atomic 是默认值,所以你不指定它,你只需要离开nonatomic 它将是原子的。还有一个问题是后台日志可能会连续多次看到obj 的相同值,而看不到obj 的某些值,因为后台日志总是从属性访问,而不是获取专门为该块调用设置的实例。
  • @Gavin strong 也是默认值,但您在答案中指定了它。
  • @AaronBrager,是的,但我所看到的 strongweak 通常是指定的。但是atomic 很少被指定。
  • @trojanfoe 感谢您的回复!您能否解释一下“后台线程有可能读取部分重新分配的对象指针”的更多细节?
  • @taichino 使用nonatomic setter/getter,当您创建obj 的新版本时,ARC 可能仍在工作,并且 getter 将获得指向已释放的指针实例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-25
相关资源
最近更新 更多