Проблема с ARC и фоновыми очередями отправки

С тех пор, как я установил последнюю операционную систему 10.8.2 и обновил Xcode до 4.5.2, у меня начались проблемы с написанным мною приложением, которое давно работает.

В приложении есть класс (CalculateTimeFiles), который имеет метод, который запускается в фоновой очереди, которая создает массив NSMutableArray типа ivar, представляющий собой массив строк. Этот массив и счетчик состояния отслеживаются вторым классом (RunResultWindow), который извлекает сообщения из NSMutableArray и отображает их в поле прокрутки текста в окне. Поскольку сообщения генерируются CalculateTimeFiles, RunResultWindow берет их и сбрасывает в текстовое представление, чтобы дать пользователю представление о том, что в настоящее время делает CalculateTimeFiles.

Этот процесс работал хорошо долгое время, но я полагаю, что в этой новой версии XCode ARC теперь включен для очередей отправки (что, я думаю, является новой вещью). Код прекрасно работает в XCode, но взрывается, когда я экспортирую приложение за пределы XCode и запускаю его там. Я обнаружил, что что-то происходит в конце фоновой задачи очереди на отправку, что приводит к тому, что все это идет полным ходом. Я предполагаю, что это работает в XCode, потому что сам XCode установил некоторую наблюдаемость для иваров в CalculateTimeFiles и предотвращает что-то от исчезновения.

Я убедился (я думаю), что строки в ivar NSMutableArray определены не в фоновой задаче, а в отдельном методе, который я перевел в основную очередь.

Любая помощь, которую я могу получить в этом, была бы великолепна.

Вот некоторые фрагменты кода, которые уместны:

Это от основного делегата приложения:

- (IBAction)runButtonPressed:(id)sender
{
......
......

 CalculateTimeFiles* tempCalculateTimeFiles = [[CalculateTimeFiles alloc] init];

RunResultWindowController = [[RunResultWindow alloc]       
           initWithWindowNibName:@"RunResultWindow"];
RunResultWindowController.localCalculateTimeFiles=tempCalculateTimeFiles;
[RunResultWindowController showWindow:self];

[self.outputfilestring1 becomeFirstResponder];

NSLog(@"before calculate time files");

[tempCalculateTimeFiles calculateOutputFiles:self];

NSLog(@"after calculate time files");


......
......
 }

Вот методы в игре в RunResultWindow:

- (void)windowDidLoad
{
[super windowDidLoad];

NSWindow *wcWindow;
wcWindow = [self window];
[wcWindow makeKeyAndOrderFront:self];

NSString *teststring;
teststring = @"start output calculations";
[RunResultWindowTextView setString:teststring];
[RunResultWindowTextView display];

localCalculateTimeFiles.localRunResultWindow = self;

[localCalculateTimeFiles addObserver:self
       forKeyPath:@"arraystatuscounter"
          options:NSKeyValueObservingOptionNew
          context:NULL];

}


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:
   (NSDictionary *)change context:(void *)context
{    

dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{ 

NSInteger arrayCountFromDictionary;
NSString* localDisplayString;
NSString* localNewlinePlusDisplayString;
NSTextStorage *tempTextStorage;

tempTextStorage = [RunResultWindowTextView textStorage];

NSLog(@"in observeValueForKeyPath before display");

arrayCountFromDictionary = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];

if (arrayCountFromDictionary != 0 ){
    arrayCountFromDictionary--;
    localDisplayString = [localCalculateTimeFiles.StatusStrings
        objectAtIndex:arrayCountFromDictionary];
    if (![localDisplayString compare: @"removeobservernow"]){
        NSLog(@"planned removeobserver logic");
        [localCalculateTimeFiles removeObserver:self forKeyPath:@"arraystatuscounter"];}
    if ([localDisplayString compare: @"removeobservernow"]){
        localNewlinePlusDisplayString = [@"\n" 
             stringByAppendingString:localDisplayString];
        [tempTextStorage beginEditing];
        [tempTextStorage replaceCharactersInRange:NSMakeRange([tempTextStorage length] -
              1, 0) withString:localNewlinePlusDisplayString];
        [tempTextStorage endEditing];
        NSLog(@"string before display %@",localDisplayString);
        [RunResultWindowTextView display];}};

NSLog(@"in observeValueForKeyPath after display");


});
}

Вот что находится в игре для CalculateTimeFiles. Обратите внимание, что я удалил вызов метода dispatch_release, потому что ARC теперь покрывает это:

@interface CalculateTimeFiles : NSObject {

NSMutableArray *StatusStrings;
NSInteger arraystatuscounter;
RunResultWindow *localRunResultWindow;

}

@property (nonatomic, retain) NSMutableArray *StatusStrings;
@property  NSInteger arraystatuscounter;
@property RunResultWindow *localRunResultWindow;

- (void) calculateOutputFiles:(id) parameterTimeGenieAppDelegate;
- (void) UpdateStatusTable:(NSString*) statusStringMessage;

@end



- (void) calculateOutputFiles:(TimeGenieAppDelegate*) parameterTimeGenieAppDelegate;
{

dispatch_queue_t backgroundQueue = dispatch_queue_create("Background Queue",NULL);
dispatch_async(backgroundQueue, ^{ 

.... does a bunch of stuff...

   [self UpdateStatusTable:@"stop here a long time"];
    [self UpdateStatusTable:@"new test string very first one"];
    [self UpdateStatusTable:@"===================="];
    [self UpdateStatusTable:@"new test string"];
    [self UpdateStatusTable:@"processing complete"];
    [self UpdateStatusTable:@"removeobservernow"];
    NSLog(@"just before dispatch release");

    // dispatch_release(backgroundQueue);

    [NSThread sleepForTimeInterval: 3.0];
    NSLog(@"just after dispatch release");
    });

NSLog(@"just after thread done");

}



- (void) UpdateStatusTable:(NSString*) statusStringMessage
{
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{

[StatusStrings addObject:[NSString stringWithString:statusStringMessage]];
[self setArraystatuscounter:[StatusStrings count]];

});

}

Я предполагаю (что может быть плохой идеей), что addObject в UpdateStatusTable, как написано, создаст новую строку, которая не исчезнет, ​​когда завершится фоновый процесс.

У меня также есть аварийные дампы, но я действительно не знаю, как их читать, так что, возможно, там есть что-то полезное, но я бы этого не знал. Единственная часть, которая имеет для меня большой смысл, такова:

Crashed Thread:  2  Dispatch queue: com.apple.root.default-overcommit-priority

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: EXC_I386_GPFLT

Thread 2 Crashed:: Dispatch queue: com.apple.root.default-overcommit-priority
0   libobjc.A.dylib                 0x00007fff8ea3bf5e objc_release + 14
1   libobjc.A.dylib                 0x00007fff8ea3b230 (anonymous
    namespace)::AutoreleasePoolPage::pop(void*) + 464
2   libdispatch.dylib               0x00007fff93eb2264 _dispatch_worker_thread2 + 410
3   libsystem_c.dylib               0x00007fff8e0b4cab _pthread_wqthread + 404
4   libsystem_c.dylib               0x00007fff8e09f171 start_wqthread + 13

Опять же, любая помощь, которую я могу оказать в этом, была бы великолепна. Заранее спасибо.

2 ответа

Решение

Скачал тестовую версию Xcode 4.6, и это решило проблему. Это были разочаровывающие 6 недель.

Проблема была на самом деле временным объектом в методе, который использовался для временного устаревания адреса объекта в NSMutableArray. Объект был объявлен в подпрограмме 'main' метода, а затем повторно использован в цикле, который перетасовывал этот адрес элемента в массиве объектов во временный объект, чтобы его можно было использовать. Когда цикл завершился, программа взорвалась вне xcode, потому что (я думаю) сам xcode установил наблюдаемость для временного объекта и не позволил ему освободиться, когда цикл (с его собственным диапазоном области действия) был освобожден. SOOOOOO... если у вас есть странные проблемы освобождения, которые происходят только вне XCode, подумайте, что 4.5.2 может быть вашей проблемой.

Arc не освобождает объекты GCD, вам нужно освободить его самостоятельно в конце блока. Смотрите мои правки ниже;

- (void) calculateOutputFiles:(TimeGenieAppDelegate*) parameterTimeGenieAppDelegate;
{

dispatch_queue_t backgroundQueue = dispatch_queue_create("Background Queue",NULL);
dispatch_async(backgroundQueue, ^{ 

.... does a bunch of stuff...

   [self UpdateStatusTable:@"stop here a long time"];
    [self UpdateStatusTable:@"new test string very first one"];
    [self UpdateStatusTable:@"===================="];
    [self UpdateStatusTable:@"new test string"];
    [self UpdateStatusTable:@"processing complete"];
    [self UpdateStatusTable:@"removeobservernow"];
    NSLog(@"just before dispatch release");
    [NSThread sleepForTimeInterval: 3.0];
    NSLog(@"just after dispatch release");
    });
dispatch_release(backgroundQueue); // dispatch_release should be here
NSLog(@"just after thread done");

}

Надеюсь, что все идет хорошо.

Другие вопросы по тегам