NSFetchedResultsController делегат, выделяющий слишком много UITableViewCells - PullRequest
1 голос
/ 16 августа 2010

Я использую 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 <NSFetchedResultsSectionInfo>)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. В консоли отладчика вывод «новая ячейка» печатается несколько раз (можно пронумеровать в сотнях), что, я полагаю, одно для каждой новой созданной сущности. Только после загрузки таблицы (если она не вылетала из-за проблем с памятью) и начала прокрутки, я вижу вывод «переработанной ячейки» в консоли.

Ответы [ 2 ]

1 голос
/ 19 августа 2010

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

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

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

1 голос
/ 16 августа 2010

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

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