NSOperation и EXC_BAD_ACCESS - PullRequest
       13

NSOperation и EXC_BAD_ACCESS

2 голосов
/ 15 марта 2011

У меня есть несколько приложений, которые в основном управляются данными, поэтому большинство экранов в основном состоят из:

  1. Откройте экран
  2. Загрузите данные с помощью NSOperation
  3. Отображение данных в UITableView
  4. Сделайте выбор из UITableView
  5. Перейдите на новый экран и начните заново с шага 1

Я обнаружил, что всеработает в обычном режиме, но если пользователь на некоторое время уходит из приложения, а затем возвращается, я получаю сообщение об ошибке EXC_BAD_ACCESS при следующем запуске NSOperation.Похоже, что это не имеет значения, отправляет ли пользователь приложение в фоновый режим или нет, и это происходит только в том случае, если прошло как минимум несколько минут с момента установления предыдущего подключения к данным.

Я понимаю, что этоДолжно быть, это какая-то форма перевыпуска, но я неплохо справляюсь с управлением памятью и не вижу в этом ничего плохого.Мои вызовы данных обычно выглядят так:

-(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 ]

1 голос
/ 15 марта 2011

попробуйте это: http://cocoadev.com/index.pl?NSZombieEnabled

также следует избегать:

1) вызов методов UIKit из вторичных потоков

2) выполнение (синхронных) запросов URL из основного потока.

вы должны делать это в любом случае в методе RequestHelper performPostRequest.

1 голос
/ 15 марта 2011

Почти наверняка вы что-то переиздавали в своем коде, видя, что сбой происходит во время [релиза пула] (в основном методе есть пул автоиздания).

Вы можете найти его с помощью XCode - используйте build and analysis, чтобы статический анализатор мог определить потенциальные проблемы. Запустите его и опубликуйте результаты.

0 голосов
/ 30 января 2016

Это действительно старый вопрос, извините за драги, но нет принятого ответа.

Я также получал EXC_BAD_ACCESS для NSOperationQueue -addOperation по-видимому без причины, и после нескольких дней поиска утечек памяти и включения всех параметров отладчика, которые я смог найти (malloc guard, zombies) и ничего не получил, я обнаружил предупреждение NSLog, в котором говорилось: «[Подкласс NSoperation] установлен в IsFinished перед запуском в очередь».

Когда я изменил свой подкласс базовой операции так, чтобы его функция -cancel только устанавливала (IsRunning = NO) и (IsFinished = YES) IF И ТОЛЬКО IF (IsRunning == YES), NSOperationQueue перестала падать.

Так что, если вы когда-либо вызываете NSOperationQueue -cancelAllOperations, или вы делаете это вручную (т. Е. Для (NSOperation * op в queue.allOperations)) двойной проверки, чтобы убедиться, что вы не установили IsFinished для этих операций в Ваша реализация подкласса.

0 голосов
/ 15 марта 2011

Я предполагаю, что этот раздел

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

Если сон происходит в плохой точке, у вас есть объект, передаваемый в другой поток.Я бы нашел способ обойти операцию как объект.

...