Проблема с NSFetchedResultsController и вставкой записей

Хорошо, это сводит меня с ума, и я пробовал все, что мог найти в Интернете, как возможное решение, но все равно ничего. Итак, вот моя ситуация: у меня есть представление, на котором есть кнопка. При прикосновении к нему появляется список клиентов, из которого пользователь может выбрать. когда они его выбирают, я использую контроллер fetchedresults, чтобы получить части, связанные с клиентом, и отобразить их в виде таблицы. Все это хорошо работает. проблема в том, что если у меня выбран клиент A и я вставляю новую деталь, затем перехожу к клиенту B и вставляю новую часть, когда я повторно выбираю клиента A и пытаюсь вставить другую часть, приложение вылетает со следующей ошибкой:

* Ошибка утверждения в - [UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1447.6.4/UITableView.m:976 2011-02-21 10: 39: 12.896 SalesPro [36203: 207] Серьезная ошибка приложения. Было обнаружено исключение от делегата NSFetchedResultsController во время вызова -controllerDidChangeContent :. Недопустимое обновление: недопустимое количество строк в разделе 0. Количество строк, содержащихся в существующем разделе после обновления (1), должно быть равно количеству строк, содержащихся в этом разделе до обновления (1), плюс или минус число. строк, вставленных или удаленных из этого раздела (1 вставлено, 0 удалено). with userInfo (null) 2011-02-21 10: 39: 12.907 SalesPro [36203: 207] * Завершение работы приложения из-за неперехваченного исключения 'NSRangeException', причина: '- [UITableView scrollToRowAtIndexPath: atScrollPosition: animated:]: row (1) за пределами (1) для секции (0). '

Код, обрабатывающий выбор клиента

-(void) CustomerSelectedRaised:(NSNotification *)notif
{
    NSLog(@"Received Notification - Customer Selected");
    selectedCustomer = (Customer *)[notif object];
    [self buildCustomerInfoText];
    if (popoverController != nil) {
        [popoverController dismissPopoverAnimated:YES];
    }

    fetchedResultsController = nil;

    NSError *error;
    if (![[self fetchedResultsController] performFetch:&error]) {
        //Update to handle error appropriately
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        exit(-1); //fail
    }

    [self.partsListGrid reloadData];
}

Код FetchedResultsController

#pragma mark -
#pragma mark Fetch results controller

- (NSFetchedResultsController *)fetchedResultsController {
    if (fetchedResultsController != nil) {
        return fetchedResultsController;
    }

    //set-up fetched results controller
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"PartsList" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];
    [fetchRequest setFetchBatchSize:20];
    NSLog(@"TAMS ID: %@", selectedCustomer.customerTAMSID);
    [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"customerTAMSID == %@", selectedCustomer.customerTAMSID]]; 

    //set to sort by customer name
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"sortOrder" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];   
    [fetchRequest setSortDescriptors:sortDescriptors];

    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] 
                                                             initWithFetchRequest:fetchRequest 
                                                             managedObjectContext:[self managedObjectContext] 
                                                             sectionNameKeyPath:nil cacheName:nil];
    [aFetchedResultsController setDelegate:self];
    [self setFetchedResultsController:aFetchedResultsController];

    //clean-up
    [aFetchedResultsController release];
    [fetchRequest release];
    [sortDescriptor release];
    [sortDescriptors release];

    //return results
    return fetchedResultsController;
}

- (void)controllerWillChangeContent:(NSFetchedResultsController*)controller 
{
    [[self partsListGrid] beginUpdates];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    // In the simplest, most efficient, case, reload the table view.
    [[self partsListGrid] endUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath {

    UITableView *tableView = self.partsListGrid;

    switch(type) {

        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath withHeight:tableView.rowHeight];
            break;

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {

    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.partsListGrid insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeDelete:
            [self.partsListGrid deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeMove:
            break;
        case NSFetchedResultsChangeUpdate: 
            break;
        default:
            break;
    }
}

Добавить код детали

-(void) addScannedPart:(Part *)part 
{
    // Check to see if entered part is already in list
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *partsEntity = [NSEntityDescription entityForName:@"PartsList" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:partsEntity];

    NSPredicate *predicate = [NSPredicate predicateWithFormat: @"customerTAMSID == %@ AND lineAbbreviation == %@ AND partNumber == %@", selectedCustomer.customerTAMSID, part.lineAbbrev, part.partNumber];
    [fetchRequest setPredicate:predicate];

    NSError *error = nil;
    NSArray *fetchedParts = [managedObjectContext executeFetchRequest:fetchRequest error:&error];

    if ([fetchedParts count] == 0) {
        //Create a new instance of the entity managed object by the fetched results controller
        NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
        NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];

        NSLog(@"Entity Name: %@", [entity name]);

        NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];

        //Add fields to Managed Object
        int sortOrder = [[fetchedResultsController fetchedObjects] count];
        sortOrder++;

        [newManagedObject setValue:[part lineAbbrev] forKey:@"lineAbbreviation"];
        [newManagedObject setValue:[part partNumber] forKey:@"partNumber"];
        [newManagedObject setValue:[NSNumber numberWithInt:[[part orderQty] intValue]] forKey:@"orderQuantity"];
        [newManagedObject setValue:selectedCustomer.customerTAMSID forKey:@"customerTAMSID"];
        [newManagedObject setValue:[NSNumber numberWithInt:sortOrder] forKey:@"sortOrder"];

        //Save the context
        NSError *error = nil;
        if (![context save:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }

        //reload Customer list 
        NSIndexPath *insertionPath = [fetchedResultsController indexPathForObject:newManagedObject];
        [self.partsListGrid selectRowAtIndexPath:insertionPath animated:YES scrollPosition:UITableViewScrollPositionTop];
        [self.partsListGrid reloadData];
    }
}

Это самый большой (самый серьезный) дефект, который я должен исправить для нашей следующей версии (которая выйдет очень скоро). Буду признателен за любую помощь! Спасибо!


person Shaggy13spe    schedule 21.02.2011    source источник


Ответы (2)


начните с исправления утечки NSFetchedResultsCOntroller в

-(void) CustomerSelectedRaised:(NSNotification *)notif
{
    /*...*/
    fetchedResultsController = nil;
    /*...*/
}

у вас не должно быть нескольких NSFetchedResultsController, которые все сообщают об изменениях в одном и том же табличном представлении. И я думаю, что это происходит. Если NSFRController не будет освобожден, он будет сообщать об изменениях своему делегату.

person Matthias Bauch    schedule 21.02.2011
comment
Да, спасибо. Если вы посмотрите на мой ответ на мой собственный вопрос, я удалил это. Я разработчик .NET, все еще изучаю Objective-C и разработку для iPhone, и больше всего меня сбивает с толку управление памятью, но я уже разбираюсь в этом. - person Shaggy13spe; 23.02.2011

хорошо, благодаря этому SO-потоку это выглядит как будто у меня это работает:

Я отредактировал метод CustomerSelectedRaised, чтобы он выглядел так:

-(void) CustomerSelectedRaised:(NSNotification *)notif
{
    NSLog(@"Received Notification - Customer Selected");
    selectedCustomer = (Customer *)[notif object];
    [self buildCustomerInfoText];
    if (popoverController != nil) {
        [popoverController dismissPopoverAnimated:YES];
    }
    //this is the new code
    NSFetchRequest *fetchRequest = [[self fetchedResultsController] fetchRequest];
    NSEntityDescription *entity = nil;
    entity = [NSEntityDescription entityForName:@"PartsList" inManagedObjectContext:managedObjectContext];
    [fetchRequest setEntity:entity];
    [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"customerTAMSID == %@", selectedCustomer.customerTAMSID]]; 

    [NSFetchedResultsController deleteCacheWithName:nil];
    // end of new code

    NSError *error;
    if (![[self fetchedResultsController] performFetch:&error]) {
        //Update to handle error appropriately
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        exit(-1); //fail
    }

    [self.partsListGrid reloadData];
}
person Shaggy13spe    schedule 21.02.2011