gpt4 book ai didi

loops - ARC 似乎过度释放了在循环中创建和调度的 block 中引用的对象

转载 作者:行者123 更新时间:2023-12-02 05:03:23 24 4
gpt4 key购买 nike

我正在尝试使用 dispatch_async 在后台线程上进行一些复杂的计算,但我在 block 中使用的对象似乎被过度释放了。我正在使用 ARC,所以我认为我不必太在意保留和释放,但在我的情况下,要么我错过了一些重要的东西,要么 ARC 过度释放了对象。

问题只有在

  • 我调用 dispatch_async 在 for 循环中创建一个 block
  • 我在 block 外创建的 block 中引用了一个对象
  • 循环至少进行两次迭代(因此至少创建两个 block 并将其添加到队列中)
  • 使用了一个 RELEASE 构建配置(所以它可能与一些优化有关)

好像没关系

  • 是串行队列还是并发队列
  • 使用什么样的对象

这个问题不是关于在 RELEASE 配置中释放的 block (如 iOS 5 blocks crash only with Release Build ),而是 block 中引用的对象被过度释放。

我使用 NSURL 对象创建了一个小示例:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSURL *theURL = [NSURL URLWithString:@"/Users/"];
dispatch_queue_t myQueue = dispatch_queue_create("several.blocks.queue", DISPATCH_QUEUE_SERIAL);

dispatch_async(myQueue, ^(){
NSURL *newURL = [theURL URLByAppendingPathComponent:@"test"];
NSLog(@"Successfully created new url: %@ in initial block", newURL);
});

for (int i = 0; i < 2; i++)
{
dispatch_async(myQueue, ^(){
NSURL *newURL = [theURL URLByAppendingPathComponent:@"test"];
NSLog(@"Successfully created new url: %@ in loop block %d", newURL, i);
});
}
}

不在 for 循环中的第一个 block 将毫无问题地工作。如果循环只有一次迭代,第二个也是如此。然而,在给定的示例中,它进行了两次迭代,如果使用 RELEASE 配置运行,将会崩溃。在方案中启用 NSZombie 输出如下:

2013-01-07 23:33:33.331 BlocksAndARC[17185:1803] Successfully created new url: /Users/test in initial block
2013-01-07 23:33:33.333 BlocksAndARC[17185:1803] Successfully created new url: /Users/test in loop block 0
2013-01-07 23:33:33.333 BlocksAndARC[17185:1803] *** -[CFURL URLByAppendingPathComponent:]: message sent to deallocated instance 0x101c32790

调试器在 for 循环 block 中的 URLByAppendingPathComponent 调用处停止。

当使用并发队列时,失败的调用实际上是调用堆栈中带有 _Block_release 的 release 调用:

2013-01-07 23:36:13.291 BlocksAndARC[17230:5f03] *** -[CFURL release]: message sent to deallocated instance 0x10190dd30
(lldb) bt
* thread #6: tid = 0x3503, 0x00007fff885914ce CoreFoundation`___forwarding___ + 158, stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0)
frame #0: 0x00007fff885914ce CoreFoundation`___forwarding___ + 158
frame #1: 0x00007fff885913b8 CoreFoundation`_CF_forwarding_prep_0 + 232
frame #2: 0x00007fff808166a3 libsystem_blocks.dylib`_Block_release + 202
frame #3: 0x00007fff89f330b6 libdispatch.dylib`_dispatch_client_callout + 8
frame #4: 0x00007fff89f38317 libdispatch.dylib`_dispatch_async_f_redirect_invoke + 117
frame #5: 0x00007fff89f330b6 libdispatch.dylib`_dispatch_client_callout + 8
frame #6: 0x00007fff89f341fa libdispatch.dylib`_dispatch_worker_thread2 + 304
frame #7: 0x00007fff852f0cab libsystem_c.dylib`_pthread_wqthread + 404
frame #8: 0x00007fff852db171 libsystem_c.dylib`start_wqthread + 13

但这可能只是由于时间略有不同。

我认为这两个错误都表明 theURL 引用的 NSURL 对象被过度释放。但这是为什么呢?我是不是漏掉了什么,或者这是 ARC 和 block 组合中的错误?

我希望发生的是,在 dispatch_async 调用之前或在 dispatch_async 的实现中(无论如何:在 for 循环内,每个 dispatch_async-call) block 内引用的每个变量都被保留,并在 block 结束时(但在 block 中)释放。

实际上似乎发生的是变量被 retain 一次,用于代码中 dispatch_async 的出现,但 release 被调用 block 的末尾,因此无论何时执行,这都会导致比循环中的 retain 调用更多的 release 调用。

但也许我忽略了一些东西。有更好的解释吗?我是否以某种方式滥用了 block 或 ARC 或者这是一个错误?

编辑:我尝试了@Joshua Weinberg 的建议,即将引用的变量复制到for 循环内的本地变量。它在给定的示例代码中有效,但在涉及函数调用时不起作用:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSObject *theObject = [[NSObject alloc] init];

[self blocksInForLoopWithObject:theObject];
}

-(void)blocksInForLoopWithObject:(NSObject *)theObject
{
dispatch_queue_t myQueue = dispatch_queue_create("several.blocks.queue", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 2; i++)
{
NSObject *theSameObject = theObject;
dispatch_async(myQueue, ^(){
NSString *description = [theSameObject description];
NSLog(@"Successfully referenced object %@ in loop block %d", description, i);
});
}
}

那么为什么它在一个案例中有效,而在另一个案例中却无效呢?我看不出有什么区别。

最佳答案

当我尝试它时,我刚刚能够重现它。您的诊断似乎是正确的,据我所知,这是一个关于 block 如何复制/保持其范围出错的优化问题。似乎值得雷达。

至于你能做些什么来解决这个问题。

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSURL *theURL = [NSURL URLWithString:@"/Users/"];
dispatch_queue_t myQueue = dispatch_queue_create("several.blocks.queue", DISPATCH_QUEUE_SERIAL);

dispatch_async(myQueue, ^(){
NSURL *newURL = [theURL URLByAppendingPathComponent:@"test"];
NSLog(@"Successfully created new url: %@ in initial block", newURL);
});

for (int i = 0; i < 2; i++)
{
NSURL *localURL = theURL;
dispatch_async(myQueue, ^(){
NSURL *newURL = [localURL URLByAppendingPathComponent:@"test"];
NSLog(@"Successfully created new url: %@ in loop block %d", newURL, i);
});
}
}

将其复制到堆栈会强制 block 每次重新捕获它并强制执行您预期的内存语义。

关于loops - ARC 似乎过度释放了在循环中创建和调度的 block 中引用的对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14205779/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com