Я пытаюсь выполнить некоторые сложные вычисления в фоновом потоке с помощью dispatch_async, но объекты, которые я использую в блоках, кажутся перегруженными. Я использую ARC, поэтому я предположил, что мне не нужно особо заботиться о сохранении и выпуске, но либо я пропустил что-то важное, либо ARC перевыпускает объекты в моем случае.
Проблема появляется только если
- Я вызываю dispatch_async, создавая блок в цикле for
- Я ссылаюсь на объект в блоке, созданном вне блока
- цикл выполняет как минимум две итерации (таким образом создаются и добавляются в очередь как минимум два блока)
- используется конфигурация сборки RELEASE (поэтому, вероятно, это связано с некоторой оптимизацией)
Кажется, это не имеет значения
- будь то последовательная или параллельная очередь
- какой предмет используется
Этот вопрос касается не выпуска блоков в конфигурации RELEASE (как в iOS 5 блоков аварийно завершает работу только при выпуске сборки), но объекты, на которые есть ссылки в блоке, перевыпущены.
Я создал небольшой пример, используя объект 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
, перегружен. Но почему? Я что-то пропустил или это баг в сочетании АРК и блоков?
Я ожидаю, что либо перед вызовом dispatch_async
, либо при реализации dispatch_async
(в любом случае: внутри цикла for, один раз для каждого dispatch_async
-вызова) каждая переменная, на которую есть ссылка внутри блока, сохраняется и освобождается в конце (но внутри) блока.
Что на самом деле происходит, так это то, что переменные retain
ed один раз для появления dispatch_async
в коде, но release
вызывается в конце блока, поэтому всякий раз, когда он выполняется, что приводит к большему количеству release
вызовов, чем retain
вызовов в цикле.
Но, возможно, я что-то упускаю из виду. Есть ли лучшее объяснение? Я неправильно использовал блоки или ARC, или это ошибка?
EDIT: я попробовал предложение @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
копирует блок, и когда блок перемещается в кучу, он сохраняет переменные, на которые ссылаются. Затем, когда блок освобождается (т. е. когда отправка выполняется), он освобождает свои ссылочные переменные. - person newacct   schedule 08.01.2013myQueue
(она не освобождается), хотя утечка никогда не вызовет проблемы, которую вы видите. - person newacct   schedule 08.01.2013