【问题标题】:NSOperation and EXC_BAD_ACCESSNSOperation 和 EXC_BAD_ACCESS
【发布时间】:2011-03-15 05:42:33
【问题描述】:

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

  1. 打开屏幕
  2. 通过 NSOperation 下载数据
  3. 在 UITableView 中显示数据
  4. 从 UITableView 中进行选择
  5. 进入新屏幕,从第 1 步重新开始

我发现一切正常使用,但如果用户离开应用程序一段时间然后回来,我会在下一个 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 分钟,这很难追踪!

如果有人能提供任何线索或方向,将不胜感激。

【问题讨论】:

  • 释放内存前是否取消网络操作?
  • 我不这么认为。我唯一一次这样做是在 viewWillDisappear 中,我循环遍历队列中的所有操作,如果 isExecuting 为真,我将 viewController 作为 isFinished 键路径的观察者删除。

标签: iphone objective-c nsoperation


【解决方案1】:

几乎可以肯定你在代码中过度释放了某些东西,看到崩溃是在 [pool release] 期间发生的(在 main 方法中也有一个自动释放池)。

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

【讨论】:

  • 虽然我同意这可能是原因,但我找不到。它似乎也不局限于一个操作——我认为它发生在任何一个操作上。我已经进行了构建和分析,但没有结果(除了一些泄漏,我已经修复了)。
【解决方案2】:

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

另外,你应该避免:

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

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

在任何情况下,您都必须在 RequestHelper 的 performPostRequest 方法中做一个。

【讨论】:

  • 我确实在第二个代码示例的底部包含了 performPostRequest 方法。我认为我没有触及任何 UIKit 方法,并且 url 请求是在后台线程上同步完成的。不过,Thsi 确实给了我一个想法,我尝试将操作的属性更改为原子的。我想我会看看情况如何?
  • @alku83 是的,我在发布之前已经看到了实现。假设您调用performPostRequest: 的唯一地方是您的操作,那么[UIApplication sharedApplication].networkActivityIndicatorVisible = BOOL 将违反#1。此外,原子属性不能替代适当的线程安全。
  • 很公平 - performPostRequest 只会从后台线程调用。不知道我是如何错过networkActivityIndicatorVisible 的,但我会将其移至主线程。关于原子属性的公平点 - 我想我只是变得绝望了。
【解决方案3】:

我猜是这个部分

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

如果睡眠发生在一个不好的地方,你有一个对象被传递给另一个线程。我会想办法绕过将操作作为对象传递。

【讨论】:

  • 可能是一个公平的观点,但我认为这不会导致我的问题。它总是在将应用程序单独放置 10 分钟后发生。如果我再次打开应用程序,然后尝试启动 NSOperation(通过点击 UITableView 行),我会收到错误消息。
【解决方案4】:

这是一个非常老的问题,很抱歉挖泥机,但没有公认的答案。

我在 NSOperationQueue -addOperation 上也得到了 EXC_BAD_ACCESS 似乎没有任何原因,经过几天寻找内存泄漏并打开我能找到的所有调试器选项(malloc 保护、僵尸)却什么也没得到,我发现一个 NSLog 警告说:“[NSoperation subclass] 在被队列启动之前设置为 IsFinished。”

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-30
    相关资源
    最近更新 更多