NSOperation和EXC_BAD_ACCESS

时间:2011-03-15 05:42:34

标签: iphone objective-c nsoperation

我有一些主要是数据驱动的应用程序,因此大多数屏幕基本上由以下部分组成:

  1. 打开屏幕
  2. 通过NSOperation下载数据
  3. 在UITableView中显示数据
  4. 从UITableView
  5. 中进行选择
  6. 转到新屏幕,从第1步开始
  7. 我发现一切都在正常使用情况下工作,但是如果用户离开应用程序一段时间然后又回来,我会在下一个NSOperation运行时收到EXC_BAD_ACCESS错误。如果用户将应用程序发送到后台,这似乎并不重要,而且只有在自上次数据连接完成后至少有几分钟时才会出现这种情况。

    我意识到这必须是某种形式的过度释放,但我的内存管理非常好,我看不出有什么不妥。我的数据通常看起来像这样:

    -(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:[GetHistoryOperation 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 
    }
    

    我的操作通常看起来像这样:

    -(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;
    }
    

    如果我有自动释放池,则会在[pool release]上发生崩溃。如果我没有,那么崩溃只是看起来出现在main.m方法中,我似乎没有得到任何有用的信息。当我每次测试之间需要等待10分钟时,很难找到它!

    如果有人可以提供任何线索或指示,那将非常感激。

4 个答案:

答案 0 :(得分:1)

几乎可以肯定,您在代码中过度发布了某些内容,发现崩溃发生在[池发布]期间(主要方法中还有自动释放池)。 / p>

您可以使用Xcode找到它 - 使用构建和分析让静态分析仪查明潜在的问题。运行它并发布结果。

答案 1 :(得分:1)

试试这个: http://cocoadev.com/index.pl?NSZombieEnabled

另外,你应该避免:

1)从辅助线程调用UIKit方法

2)从主线程发出(同步)url请求。

你必须在RequestHelper的performPostRequest方法中做任何一个。

答案 2 :(得分:0)

我的猜测是本节

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

如果睡眠发生在这里的坏点,你有一个对象被传递给另一个线程。我找到了一种方法来将操作作为对象传递。

答案 3 :(得分:0)

这是一个非常古老的问题,对于疏浚很抱歉,但没有接受的答案。

我在NSOperationQueue -addOperation上也得到了一个EXC_BAD_ACCESS看似没有理由,经过几天的内存泄漏,并打开了我能找到的所有调试器选项(malloc后卫,僵尸),什么也没得到,我发现一条NSLog警告说:" [NSoperation subclass]在被队列启动之前设置为IsFinished。"

当我修改我的基本操作子类时,它的-cancel函数只设置(IsRunning = NO)和(IsFinished = YES)IF和ONLY IF(IsRunning == YES),NSOperationQueue停止崩溃。

因此,如果您曾经调用过NSOperationQueue -cancelAllOperations,或者您手动执行此操作(即(NSOperation * op in queue.allOperations))请仔细检查以确保您不要在子类实现中的那些操作上设置IsFinished。