Приложение аварийно завершает работу при доступе к NSManagedObjects определенным методом - PullRequest
1 голос
/ 20 сентября 2009

У меня есть метод в UIViewController моего приложения для iPhone (внутри UINavigationController), который вызывается всякий раз, когда строка выбирается в таблице в представлении ViewController. В этом методе я обращаюсь к массиву «Dream», хранящемуся в поле экземпляра dreamsArray, которое содержит NSManagedObjects из моей базы данных. Я могу получить доступ к объектам из этого массива другими методами, но, похоже, что всякий раз, когда я пытаюсь получить или изменить извлеченные объекты из этого массива в этом конкретном методе, программа вылетает.

Вот как создается DreamArray:

    dreamsArray = [[NSMutableArray alloc] init];

    [self managedObjectContext];

    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Dream" inManagedObjectContext:managedObjectContext];
    [request setEntity:entity];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"title" ascending:NO];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    [request setSortDescriptors:sortDescriptors];
    [sortDescriptors release]; [sortDescriptor release];

    NSError *error;
    NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
    if ( mutableFetchResults == nil )
        NSLog(@"oh noes! no fetch results DreamsTabController:45");

    dreamsArray = [mutableFetchResults mutableCopy];
    [mutableFetchResults release];
    [request release];

Экземпляр, в котором работает запрос dreamsArray и его объектов:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellID = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if ( cell == nil )
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID] autorelease];

    Dream *dream = (Dream *)[dreamsArray objectAtIndex:indexPath.row];

    cell.textLabel.text = [dream title];
    cell.detailTextLabel.text = @"foo!";

    [dream release];

    return cell;
}

И метод, который имеет все проблемы:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    Dream *dream = (Dream *)[dreamsArray objectAtIndex:indexPath.row];
    // BOOM - crashes right here
    EditDreamController *edit = [[EditDreamController alloc] initWithNibName:@"EditDream" bundle:nil];
    edit.dream = [[NSArray alloc] initWithObjects:dream.dreamContent, nil];
    [navigationController pushViewController:edit animated:YES];
    [dream release];
    [edit release];
}

Приложение вылетает сразу после запроса dreamsArray.

Даже вызов простого NSLog(@"%@", dream.title) в этом методе вызывает сбой. Что может быть не так?

Ответы [ 2 ]

27 голосов
/ 20 сентября 2009

Следующий код короче, эффективнее, легче для чтения и не имеет примерно шести утечек памяти вашего первого блока:

NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"Dream" inManagedObjectContext:[self managedObjectContext]]];

static NSArray *sortDescriptors = nil;
if (!sortDescriptors)
    sortDescriptors = [[NSArray alloc] initWithObject:[[[NSSortDescriptor alloc] initWithKey:@"title" ascending:NO] autorelease]];
[request setSortDescriptors:sortDescriptors];

NSError *error = nil;
NSArray *fetchResults = [managedObjectContext executeFetchRequest:request error:&error];
if (!fetchResults)
    NSLog(@"oh noes! no fetch results DreamsTabController:45, error %@", error);
[request release];

dreamsArray = [NSMutableArray arrayWithArray:fetchResults];

Этот метод переписан, чтобы быть меньше и не переусердствовать мечтой, что приводит к сбоям:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
{
    static NSString *cellID = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID] ? : [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID] autorelease];

    Dream *dream = [dreamsArray objectAtIndex:indexPath.row];
    cell.textLabel.text = dream.title;
    cell.detailTextLabel.text = @"foo!";

    return cell;
}

Этот метод имел избыточный выпуск 'dream' и утечку в NSArray, а значит, и экземпляр 'dream'.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
{
    EditDreamController *editDreamController = [[EditDreamController alloc] initWithNibName:@"EditDream" bundle:nil];

    Dream *dream = [dreamsArray objectAtIndex:indexPath.row];
    editDreamController.dream = [NSArray arrayWithObjects:dream.dreamContent];

    [navigationController pushViewController:editDreamController animated:YES];
    [editDreamController release];
}

Не ясно, когда переменная экземпляра в EditDreamController является единственной, когда она принимает массив - это должны быть «мечты», если вы действительно можете установить более одного из них.

-Wil

8 голосов
/ 20 сентября 2009
Dream *dream = (Dream *)[dreamsArray objectAtIndex:indexPath.row];

cell.textLabel.text = [dream title];
cell.detailTextLabel.text = @"foo!";

[dream release];

Вы не должны выпускать -dream. Массив удерживает его. То же самое относится и к методу -tableView:didSelectRowAtIndexPath:. Скорее всего, объект освобождается достаточно раз, чтобы его можно было освободить, оставляя в массиве висячую ссылку.

Конечный результат?

Авария.

Кроме того, ваш первый бит кода имеет:

dreamsArray = [[NSMutableArray alloc] init];

[self managedObjectContext];

NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Dream" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"title" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release]; [sortDescriptor release];

NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if ( mutableFetchResults == nil )
    NSLog(@"oh noes! no fetch results DreamsTabController:45");

dreamsArray = [mutableFetchResults mutableCopy];

Здесь есть несколько битов запутанного кода.

(1) Почему вы устанавливаете dreamsArray в качестве пустого изменяемого массива, а затем сбрасываете его, чтобы ссылаться на изменчивую копию результатов запроса на выборку? Вы пропускаете пустой изменяемый массив.

(2) Вы звоните [self managedObjectContext], но ничего не делаете с возвращаемым значением. Тогда вы используете managedObjectContext напрямую. Просто используйте [self managedObjectContext] везде. Накладные расходы незначительны.

(3) Вы создаете запрос на сохранение и присваиваете ему request, но никогда не отпускаете его. Очередная утечка памяти.

(4) Почему вы копируете mutableFetchResults дважды? Это не имеет никакого смысла (и ведет к очередной утечке).

В целом, я бы предложил пересмотреть документацию по управлению памятью Objective-C.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...