我想使用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