我想使用dispatch_async在后台线程上放置一些复杂的计算,但我在块中使用的对象似乎被过度释放。我使用的是ARC,所以我认为我不需要太在意保留和释放,但是我错过了一些重要的东西,或者在我的情况下放过ARC。ARC似乎过度释放在循环中创建和分派的块中引用的对象
问题仅当
- 我请dispatch_async创建一个块在for循环中
- 出现我在块的外部创建的块表示一个对象
- 环路确实至少两次迭代(因此至少创建了两个块并添加到队列中)
- 使用RELEASE构建配置(因此它可能与某些优化有关)
这似乎并不重要
- 无论是串行或并行队列
- 什么样的对象是使用
这个问题是不是块在发布配置为发布(如iOS 5 blocks crash only with 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
与调试器在for循环块中的通话URLByAppendingPathComponent
停止。
在使用并发队列失败的通话实际上是在调用堆栈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
但这可能只是因为稍有不同的时间。
我认为这两个错误都表明由theURL
引用的NSURL对象被过度发布。但为什么呢?我错过了什么,或者是ARC和块组合中的错误?
我会希望发生的是,无论是之前dispatch_async
呼叫或在dispatch_async
实施(反正:里面的for循环,每进行一次dispatch_async
-call)的块中引用的每个变量可以被保留,这是在(但在)块的末尾发布。
什么实际上似乎发生的是,变量是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);
});
}
}
那么,为什么它在工作的情况下,而不是在其他?我没有看到区别。
“我希望发生的是无论是在dispatch_async调用之前还是在dispatch_async调用之前(无论如何:在for循环中,对于每个dispatch_async调用一次),块中引用的每个变量都将保留,它是在(但在)块的末尾发布。“更具体地说,应该发生的是'dispatch_async'的实现复制块,并且当块移动到堆时,它保留所引用的变量。然后,当块被解除分配时(即,当分派完成时),它释放其引用的变量。 – newacct
我也注意到你正在泄漏'myQueue'队列(它没有被释放),尽管泄漏永远不会导致你所看到的问题。 – newacct
我以前曾遇到过这个问题,但似乎它不再出现在最新的XCode(4.6)上,并带有命令行工具的最新更新。我的clang版本似乎是“Apple LLVM 4.2版(clang-425.0.24)(基于LLVM 3.2svn)” – user102008