开发者

NSOperation and EXC_BAD_ACCESS

I have a few apps which are largely data driven, so most screens are basically composed of:

  1. Open the screen
  2. Download the data via an NSOperation
  3. Display data in a UITableView
  4. Make a selection from the UITableView
  5. Go to new screen, and start over from step 1

I am finding that everything works in normal usage, but if the user goes away from the app for a while and then comes back, I'm getting an EXC_BAD_ACCESS error when the next NSOperation runs. This doesn't seem to matter if the user sends the app into the background or not, and it only seems to occur if there's been at least a few mins since the previous data connection was made.

I realise this must be some form of over-releasing, but I'm pretty good with my memory management and I can't see anything wrong. My data calls generally look like this:

-(void)viewDidLoad {
    [super viewDidLoad];

    NSOperationQueue* tmpQueue = [[NSOperationQueue alloc] init];
    self.queue = tmpQueue;
    [tmpQueue release];
}

-(void)loadHistory {
    GetHistoryOperation* operation = [[GetHistoryOperation alloc] init];
    [operation addObserver:self forKeyPath:@"isFinished" options:0 context:NULL];
    [self.queue addOperation:operation];
    [operation release];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqual:@"isFinished"] && [object isKindOfClass:[GetHistory开发者_StackOverflow中文版Operation class]]) {
        GetHistoryOperation* operation = (GetHistoryOperation*)object;
        if(operation.success) {
            [self performSelectorOnMainThread:@selector(loadHistorySuceeded:) withObject:operation waitUntilDone:YES];
        } else {
            [self performSelectorOnMainThread:@selector(loadHistoryFailed:) withObject:operation waitUntilDone:YES];
        }       
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

-(void)loadHistorySuceeded:(GetHistoryOperation*)operation {
    if([operation.historyItems count] > 0) {
        //display data here
    } else {
        //display no data alert
    }
}

-(void)loadHistoryFailed:(GetHistoryOperation*)operation {
    //show failure alert 
}

And my operations generally looks something like this:

-(void)main {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSError* error = nil;
    NSString* postData = [self postData];
    NSDictionary *dictionary = [RequestHelper performPostRequest:kGetUserWalkHistoryUrl:postData:&error];

    if(dictionary) {
        NSNumber* isValid = [dictionary objectForKey:@"IsValid"];
        if([isValid boolValue]) {
            NSMutableArray* tmpDays = [[NSMutableArray alloc] init];
            NSMutableDictionary* tmpWalksDictionary = [[NSMutableDictionary alloc] init];
            NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
            [dateFormatter setDateFormat:@"yyyyMMdd"];

            NSArray* walksArray = [dictionary objectForKey:@"WalkHistories"];
            for(NSDictionary* walkDictionary in walksArray) {
                Walk* walk = [[Walk alloc] init];
                walk.name = [walkDictionary objectForKey:@"WalkName"];
                NSNumber* seconds = [walkDictionary objectForKey:@"TimeTaken"];
                walk.seconds = [seconds longLongValue];

                NSString* dateStart = [walkDictionary objectForKey:@"DateStart"];
                NSString* dateEnd = [walkDictionary objectForKey:@"DateEnd"];
                walk.startDate = [JSONHelper convertJSONDate:dateStart];
                walk.endDate = [JSONHelper convertJSONDate:dateEnd];

                NSString* dayKey = [dateFormatter stringFromDate:walk.startDate];
                NSMutableArray* dayWalks = [tmpWalksDictionary objectForKey:dayKey];
                if(!dayWalks) {
                    [tmpDays addObject:dayKey];
                    NSMutableArray* dayArray = [[NSMutableArray alloc] init];
                    [tmpWalksDictionary setObject:dayArray forKey:dayKey];
                    [dayArray release];
                    dayWalks = [tmpWalksDictionary objectForKey:dayKey];
                }
                [dayWalks addObject:walk];
                [walk release];
            }

            for(NSString* dayKey in tmpDays) {
                NSMutableArray* dayArray = [tmpWalksDictionary objectForKey:dayKey];

                NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"startDate" ascending:YES];
                NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
                NSArray* sortedDayArray = [dayArray sortedArrayUsingDescriptors:sortDescriptors];
                [sortDescriptor release];

                [tmpWalksDictionary setObject:sortedDayArray forKey:dayKey];
            }

            NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:nil ascending:NO selector:@selector(localizedCompare:)];
            self.days = [tmpDays sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
            self.walks = [NSDictionary dictionaryWithDictionary:tmpWalksDictionary];
            [tmpDays release];
            [tmpWalksDictionary release];
            [dateFormatter release];
            self.success = YES;
        } else {
            self.success = NO;
            self.errorString = [dictionary objectForKey:@"Error"];
        }
        if([dictionary objectForKey:@"Key"]) {
            self.key = [dictionary objectForKey:@"Key"];
        }
    } else {
        self.errorString = [error localizedDescription];
        if(!self.errorString) {
            self.errorString = @"Unknown Error";
        }
        self.success = NO;
    }

    [pool release];
}

-(NSString*)postData {
    NSMutableString* postData = [[[NSMutableString alloc] init] autorelease];

    [postData appendFormat:@"%@=%@", @"LoginKey", self.key];

    return [NSString stringWithString:postData];
}

----
@implementation RequestHelper

+(NSDictionary*)performPostRequest:(NSString*)urlString:(NSString*)postData:(NSError**)error {
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

    NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", kHostName, urlString]];
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
    [urlRequest setHTTPMethod:@"POST"];
    if(postData && ![postData isEqualToString:@""]) {
        NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];
        [urlRequest setHTTPBody:[postData dataUsingEncoding:NSASCIIStringEncoding]];
        [urlRequest setValue:postLength forHTTPHeaderField:@"Content-Length"];
        [urlRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    }

    NSURLResponse *response = nil;  
    error = nil;
    NSData *jsonData = [NSURLConnection sendSynchronousRequest:(NSURLRequest *)urlRequest returningResponse:(NSURLResponse **)&response error:(NSError **)&error];

    NSString *jsonString = [[NSString alloc] initWithBytes: [jsonData bytes] length:[jsonData length]  encoding:NSUTF8StringEncoding];
    NSLog(@"JSON: %@",jsonString);

    //parse JSON
    NSDictionary *dictionary = nil;
    if([jsonData length] > 0) {
        dictionary = [[CJSONDeserializer deserializer] deserializeAsDictionary:jsonData error:error];
    }

    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

    return dictionary;
}

If I have the autorelease pool in place, the crash occurs on [pool release]. If I don't, then the crash just looks to appear in the main.m method, and I don't seem to get any useful information. It's difficult to track down when I have to wait 10 mins in between every test!

If anyone can offer any clues or directions to go, that'd be much appreciated.


It's almost certain you're overreleasing something in your code, seeing that the crash is occurring during a [pool release] (There's a autorelease pool in the main method as well).

You can find it using Xcode - use build and analyze to have the static analyser pinpoint potential problems. Run it and post the results.


try this: http://cocoadev.com/index.pl?NSZombieEnabled

also, you should avoid:

1) calling UIKit methods from secondary threads

2) making (synchronous) url requests from the main thread.

you must be doing one in any case in RequestHelper's performPostRequest method.


My guess is this section

    GetHistoryOperation* operation = (GetHistoryOperation*)object;
    if(operation.success) {
        [self performSelectorOnMainThread:@selector(loadHistorySuceeded:) withObject:operation waitUntilDone:YES];
    } else {
        [self performSelectorOnMainThread:@selector(loadHistoryFailed:) withObject:operation waitUntilDone:YES];
    }       

If the sleep happens at a bad point here, you have an object being passed to another thread. I'd find a way around having to pass the operation as the object.


This is a really old question, so sorry for the dredge, but there is no accepted answer.

I was also getting a EXC_BAD_ACCESS on NSOperationQueue -addOperation for seemingly no reason, and after a few days of hunting down memory leaks, and turning on all the debugger options i could find (malloc guard, zombies) and getting nothing, I found an NSLog warning that said: "[NSoperation subclass] set to IsFinished before being started by the queue."

When I modified my base operation subclass, so that its -cancel function only set (IsRunning = NO) and (IsFinished = YES) IF AND ONLY IF (IsRunning == YES), NSOperationQueue stopped crashing.

So if you're ever calling NSOperationQueue -cancelAllOperations, or you're doing that manually (i.e. for (NSOperation *op in queue.allOperations) ) double check to make sure that you don't set IsFinished on those operations in your subclass implementation.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜