Search code examples
objective-cxcode4.5observer-patterngrand-central-dispatchcrash-dumps

Trouble with ARC and background dispatch queues


Since, I've installed the latest 10.8.2 Operating system and upgraded Xcode to 4.5.2, I've started to get problems with an application I wrote which has been working for a long time.

The application has a class (CalculateTimeFiles) which has a method which runs itself on a background queue which builds a ivar NSMutableArray which is an array of strings. This array and a status counter is observed by a second class (RunResultWindow) which takes the messages off of the NSMutableArray and displays them in a scrolling text view box within a window. As the messages are generated by CalculateTimeFiles, RunResultWindow picks them up and drops them into the text view to give the user an idea of what CalculateTimeFiles is doing at the moment.

This process has worked fine for a long time but I guess in this new version of Xcode, ARC is now enabled for dispatch queues (which I think is a new thing). The code runs fine in Xcode but blows up when I export the application outside of Xcode and run it there. What I am finding is that something happens at the end of the background dispatch queue task which causes the whole thing to go belly up. I assume that it is working in Xcode because Xcode itself has established some observability to the ivars in CalculateTimeFiles and keeping something from disappearing.

I've made sure (I think) that the strings in the ivar NSMutableArray are not defined in the background task but instead in a separate method which I forced to the main queue.

Any help I can get on this would be great.

Here are some code snippets which are pertinent:

This is from the main app delegate:

- (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");


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

Here are the methods in play in 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");


});
}

Here is what is in play for CalculateTimeFiles. Please note that I removed the dispatch_release method call because ARC now cover this:

@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]];

});

}

I am assuming (which may be a bad idea) that the addObject in the UpdateStatusTable as written will create a new string which will not go away when the background process completes.

I also do have crash dumps but I am really not sure how to read them so maybe there is something in there which would be useful but I wouldn't know it. The only part of it that makes much sense to me is this:

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

Again, any help I can get on this would be great. Thanks in advance.


Solution

  • Downloaded test version of Xcode 4.6 and that cleared up the problem. THAT was a frustrating 6 weeks.

    Problem was actually a temporary object in a method which was used to temporarily old an address of an object in a NSMutableArray. The object was declared in the 'main' routine of the method and then repeatedly reused in a loop that shuffled that address of the item in the object array to the temporary object so it could be used. When the loop ended the program blew up outside of xcode because (I think) xcode itself had established observability to the temporary object and kept it from being deallocated when the loop (with it's own scoping range) was deallocated. SOOOOOO... if you're having weird deallocation problems that only happens outside of Xcode, consider that 4.5.2 might be your problem.