Блок Objective-C не выпускается для фоновых приложений


У меня есть приложение, которое работает только в фоновом режиме (указав LSBackgroundOnly в информации.файл plist). Проблема в том, что все блоки, которые я запускаю в параллельных очередях, не освобождаются. Код выполняется в среде, управляемой памятью-GC не задействован.

(упрощенный) код выглядит следующим образом. Blubber - это просто какой-то фиктивный класс, который содержит NSDate для тестирования. Кроме того, он перезаписывает retain, release, и dealloc чтобы сделать некоторые записи:

NSOperationQueue *concurrentQueue = [[NSOperationQueue alloc] init];
[concurrentQueue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount];

Blubber *aBlubber = [[Blubber alloc] init]; 
aBlubber.aDate = [NSDate date];

[concurrentQueue addOperationWithBlock:^{       
NSAutoreleasePool *blockPool = [[NSAutoreleasePool alloc] init];
    NSDate *test = [aBlubber aDate];
    NSLog(@"Block DONE");
    [blockPool release];    
}];

[aBlubber release];

[concurrentQueue release];

Если я изменю приложение чтобы быть нормальным (т. е. не бэкграундным) приложением, я могу наблюдать, как блоки освобождаются всякий раз, когда какой-либо ввод осуществляется через пользовательский интерфейс (достаточно даже изменить фокус на другое окно). Поскольку мое приложение backgorund получает ввод непосредственно через драйвер HID USB, и у него нет окна или строки меню, этого не происходит.

Существует ли какой-либо способ вручную принудить runloop или что-то еще, ответственное за то, чтобы сообщить очередям, чтобы освободить готовые блоки?

(все другие объекты, которые сохранившиеся блоки также не освобождались, создавая огромные утечки памяти. Эти утечки не могут быть обнаружены с помощью инструментов утечки или выделения объектов, но потребление памяти может наблюдаться стремительный рост с помощью top.)

2 3

2 ответа:

Один общий "gotcha" для пулов авторелиза заключается в том, что если приложение создает память без получения событий, то самый внешний пул (управляемый циклом событий) не будет сливаться.

Я не думаю, что это должно применяться здесь, так как вы управляете своим собственным пулом... но на всякий случай, вы можете попробовать это:

...
//When no events are coming in (i.e. the user is away from their computer), the runloop doesn't iterate, and we accumulate autoreleased objects
[[NSTimer scheduledTimerWithTimeInterval:60.0f target:self selector:@selector(kickRunLoop:) userInfo:nil repeats:YES] retain];
...
- (void) kickRunLoop:(NSTimer *)dummy
{
// Send a fake event to wake the loop up.
[NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
                                    location:NSMakePoint(0,0)
                               modifierFlags:0
                                   timestamp:0
                                windowNumber:0
                                     context:NULL
                                     subtype:0
                                       data1:0
                                       data2:0]
         atStart:NO];
}

Похоже, что вы используете блок на основе стека, который используется после того, как блок вышел из области видимости. Блок должен быть скопирован. Код должен работать, если он изменен на этот:

[concurrentQueue addOperationWithBlock:[[^{       
    NSAutoreleasePool *blockPool = [[NSAutoreleasePool alloc] init];
    NSDate *test = [aBlubber aDate];
    NSLog(@"Block DONE");
    [blockPool release];    
}copy]autorelease]];

Взгляните на этот пост для полной записи по блокам: http://gkoreman.com/blog/2011/02/27/blocks-in-c-and-objective-c/