【问题标题】:Using blocks and ARC is causing a "message sent to deallocated instance" error使用块和 ARC 导致“发送到已释放实例的消息”错误
【发布时间】:2012-06-04 00:20:19
【问题描述】:

我遇到了 ARC 和块的问题,但问题已解决。不幸的是,我不知道到底发生了什么,并想了解更多关于我所遇到的情况。

最初,我有这样做的代码

for(__block id<Foo> object in objects) {
    foo download:someParm
         success:^{
            object.state = StateNewState; 
         }
    ];
}

这导致保留不平衡。当一个对象被访问并且据说已经被释放时,就会发生崩溃。我编写了一个实现并使用“复制”属性创建一个成功块属性的类,该属性保存了传递给下载函数成功参数的块。我用以下代码替换了代码

for(id<Foo> object in objects) {
    foo download:someParm
         success:^(id<Foo> successObject){
            successObject.state = StateNewState; 
         }
    ];
}

没有更多的释放对象错误,但我还没有运行工具来检查我是否没有泄漏。一些如何使用 __block 导致对象被释放太多次,我不知道为什么。我将继续研究这个问题的原因,但我认为这对你们其他人来说是一个有趣的问题。

我想可能值得注意的是 objects 数组是一个自动释放的数组,它是在我在本文前面写下的代码的行中创建的。不要认为这很重要,但我想我会在那里度过。我在这篇文章中的代码不是确切的代码,因为我正在使用它来工作并且那里有一堆绒毛。但是在 for 循环中没有创建其他对象。

当应用程序崩溃时,它会运行下载,然后运行回调,顺便说一下,我正在使用 ASIHttp。当我尝试再次下载时,它会运行并且不会调用回调,因为对象已被释放并且委托被取消。在此之后,当包含指向对象的指针的字典访问对象时,我们会崩溃。

【问题讨论】:

  • 我正在阅读文档并被提醒使用 __block 不会增加实例 ivars 的保留计数,方法是使用指向 self 的 __block 指针来引用块中的变量。我想知道使用 __block 不会增加对象在 id 对象的保留计数是否也是真的?因此在释放块时导致保留不平衡导致 id 对象也被释放?

标签: ios automatic-ref-counting objective-c-blocks


【解决方案1】:

Blocks Programming Topics 说:

在块内使用实例变量会导致对象 本身要保留。如果您希望覆盖此行为 特定的对象变量,你可以用 __block 存储标记它 类型修饰符。

如果您使用 ARC,对象变量会在复制块并稍后释放时自动保留和释放。

因此,如果您不使用__block,我想您会发现您的变量正在为您保留。例如,这似乎对我有用:

NSMutableArray *array = [[NSMutableArray alloc] init];

// add two custom objects to that array

[array addObject:[[MyObject alloc] initWithText:@"One" number:1]];
[array addObject:[[MyObject alloc] initWithText:@"Two" number:2]];
[array addObject:[[MyObject alloc] initWithText:@"Three" number:3]];

// now replace the text field in each of the three objects with the word "Done"

for (MyObject *object in array)
{
    [self blockTestInvocation:^{
        NSLog(@"%s %@", __FUNCTION__, [NSDate date]);

        object.text = @"Done";
    }];
}

当我的blockTestInvocation 同步调用传递的块时,我发现__block 的存在或不存在没有实质性影响,但是当我将它设置为异步调用块时(在我的数组和对象将否则已被释放),__block 的缺失确保了该对象被保留,从而防止了可怕的“发送到已释放实例的消息”(并且也没有泄漏)。但是使用__block,对象不会被保留,因此在代码块尝试引用它时可能会被释放。

【讨论】:

  • 感谢您的帮助。我一定要注意 __block 如何防止对象被自动保留。
【解决方案2】:

两件事:

1) 与您所描述的内容不符。你说你把这些对象放在一个自动释放的数组中,这可能是为了运行循环而临时存储的。然后,您的块回调在这些对象上设置了一些状态,除非其他东西也保留它们,否则这将毫无意义。所以你的问题出在其他东西上——无论创建这些对象是什么,都应该在需要它们时保留它们——大概足够长的时间来观察状态变化。如果是这样,您将不会收到 EXC_BAD_ACCESS 错误。

2) 您的循环中不需要 __block 限定符。它基本上是在告诉编译器您的块可能会为该引用分配一个新对象,因此它需要取消引用该变量。但你的块不这样做。您只是向对象发送消息。如果您不使用 __block 限定符,您的块将获取该值的 const 副本——该值是指向您的对象的指针。然后,当您执行object.state = StateNewState 时,您正在向该指针处的对象发送setState:newState 消息。所以这应该可以正常工作:

for(id<Foo> object in objects) {
    foo download:someParm
         success:^{
            object.state = StateNewState; 
         }
    ];
}

【讨论】:

  • 我同意这个观点,即为一个你不打算保留的对象设置一个状态有点奇怪。我认为这只是超级简化示例的构造,而不是实际的真实场景。但我可以想象,如果您已经关闭了启动下载的视图,并且在下载完成时该视图已被关闭,那么我可以想象这种问题会出现。
  • 我不应该把那个位和数组放在一起。忽略它。
  • 对第 2 点的深入了解。我在一天结束时会脑死亡,但你说的是对的。不知道我在想什么。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-17
  • 2011-06-16
相关资源
最近更新 更多