我正在尝试使用 dispatch_async 在后台线程上进行一些复杂的计算,但是我在块中使用的对象似乎被过度释放了。我正在使用 ARC,所以我认为我不必太在意保留和释放,但在我的情况下,要么我错过了一些重要的东西,要么 ARC 过度释放了对象。
仅在以下情况下才会出现问题
- 我调用 dispatch_async 在 for 循环中创建一个块
- 我在块外创建的块中引用了一个对象
- 循环至少进行两次迭代(因此至少创建了两个块并将其添加到队列中)
- 使用了 RELEASE 构建配置(因此它可能与一些优化有关)
这似乎无关紧要
- 无论是串行队列还是并发队列
- 使用什么样的对象
这个问题不是关于在 RELEASE 配置中释放的块(如在iOS 5 块中仅使用 Release Build 崩溃),而是在块中引用的对象被过度释放。
我使用 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 循环中的第一个块将毫无问题地工作。如果循环只有一次迭代,第二个也是如此。然而,在给定的示例中,它会执行两次迭代,如果使用 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
URLByAppendingPathComponent
调试器在 for 循环块中的调用处停止。
当使用并发队列时,失败的调用实际上是release
调用堆栈中带有 _Block_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
但这可能只是由于时间略有不同。
我认为这两个错误都表明引用的 NSURL 对象theURL
被过度释放。但这是为什么呢?我错过了什么,还是 ARC 和块组合中的错误?
我期望发生的是,在dispatch_async
调用之前或在执行dispatch_async
(无论如何:在for循环内,每次dispatch_async
调用一次)块内引用的每个变量都被保留并在结束时释放(但是在)块。
实际上似乎发生的是,变量在代码中retain
出现一次,但在块的末尾被调用,所以每当它被执行时,这会导致比循环中的调用更多的调用。dispatch_async
release
release
retain
但也许我忽略了一些东西。有更好的解释吗?我是否以某种方式滥用了块或 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);
});
}
}
那么为什么它在一个案例中起作用,而在另一个案例中却不起作用呢?我看不出有什么区别。