NSFetchedResultsController делегат, выделяющий слишком много UITableViewCells

Я использую NSFetchedResultsController стандартным способом для обновления UITableView всякий раз, когда что-то изменяется в связанных объектах базовых данных. Я делаю то же самое, что описано в документации Apple .

У меня проблема, когда я делаю массовую вставку новых сущностей с основными данными. Это приводит к тому, что делегат NSFetchedResultsController выделяет (и вставляет) новую ячейку для каждой сущности, но делает это без повторного использования UITableViewCells (т. Е. dequeueReusableCellWithIdentifier: всегда возвращает ноль). Это означает выделение потенциально сотен UITableViewCells, что может привести к проблемам с памятью. Кто-нибудь знает об исправлении или обходном пути? Спасибо.

Редактировать 1:

В моем подклассе UITableViewController у меня есть стандартные методы NSFetchedResultsControllerDelegate. Я считаю, что это идентично примеру от Apple.

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

    UITableView *tableView = self.tableView;

    switch(type) {

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

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

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

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationBottom];
            // Reloading the section inserts a new row and ensures that titles are updated appropriately.
            [tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationTop];
            break;
    }
}

-(void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    switch(type) {          
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationTop];
            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationBottom];
            break;
    }
}

-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    // The fetch controller has sent all current change notifications, so tell the table view to process all updates.
    [self.tableView endUpdates];
}

Также в моем подклассе UITableViewController у меня есть следующее для извлечения ячейки для заданного indexPath:

-(void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    Waypoint *waypoint = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = [waypoint comment];
}

-(UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {    
    static NSString *CellIdentifier = @"WaypointCell"; // matches identifier in XIB

    UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        NSLog(@"new cell");
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];      
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    } else {
        NSLog(@"recycled cell");    
    }

    [self configureCell:cell atIndexPath:indexPath];

    return cell;
}

В tableView: cellForRowAtIndexPath: я добавил функцию Заявления NSLog для отображения того, что происходит.

Вот что происходит. Вышеупомянутый UITableViewController помещается в стек навигации. Асинхронный запрос отправляется и извлекает кучу данных, а также создает или изменяет данные, связанные с этим fetchController. Как только вызывается [self.tableView endUpdates] , система начинает создавать и вставлять UITableViewCells в UITableView. В консоли отладчика вывод «новая ячейка» печатается несколько раз (можно пронумеровать в сотнях), что, я полагаю, одно для каждой новой созданной сущности. Только после загрузки таблицы (если она не вылетала из-за проблем с памятью) и начала прокрутки, я вижу вывод «переработанной ячейки» в консоли.

1
задан chris 17 August 2010 в 10:19
поделиться

2 ответа

Я нашел решение, которое меня устраивает. Проблема не в NSFetchedResultsController как таковом, а в том, что он вызывает [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationTop] потенциально сотни раз из делегата NSFetchedResultsController.

Мое решение - ввести булево значение massUpdate, которое контролирует, должен ли NSFetchedResultsController вставлять новые строки таблицы, как описано выше, или вместо этого он должен сделать простое [tableView reloadData]. Я устанавливаю этот параметр в зависимости от количества строк, которые я планирую вставить. Подробности реализации можно найти в следующем сообщении форума:

https://devforums.apple.com/message/181219#181219

1
ответ дан 2 September 2019 в 22:08
поделиться

Вы уверены, что используете один и тот же идентификатор для создания и удаления ячейки из очереди? (тот же идентификатор для dequeueReusableCellWithIdentifier: и initWithStyle: reuseIdentifier: )

1
ответ дан 2 September 2019 в 22:08
поделиться