Сбой в UITableView endUpdates при перемещении последней строки в разделе - PullRequest
10 голосов
/ 04 ноября 2011

У меня есть UITableViewController, который поддерживается NSFetchedResultsController.

Мой NSFetchedResultsController помещает результаты в два раздела на основе логического значения.

В фоновом потоке источник данных изменяется так, что строки добавляются или удаляются. У меня есть NSManagedObjectContext слияния моего фонового потока правильно, и эта ситуация работает нормально для большинства случаев. Строки изменяются при изменении данных и перемещаются между разделами с анимацией.

Однако есть одна ситуация, когда мое приложение аварийно завершает работу с EXC_BAD_ACCESS. Случай, когда последняя строка в разделе перемещается в другой раздел. (Трассировка стека ниже). Сбой происходит в objc_msgSend, но обычные подсказки по отладке Я не использую ничего полезного, я просто получаю "Значение не может быть преобразовано в целое число" из gdb.

Методы NSFetchedResultsControllerDelegate вызываются в следующем порядке:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller;
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type;
     // ^ The parameters specify a deletion of the 1st section
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath;
     // ^ The parameters specify a moved row from the 1st section to the 1st section 
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;

Просматривая StackOverflow, многие люди, которые сталкиваются с подобными проблемами, поддерживают свой UITableViewController вручную, используя NSArray или NSMutableArray, и они просто не обновляют источник данных в нужное время. В этом случае NSFetchedResultsController обрабатывает возвращение количества строк и разделов и определенно обновляется к моменту вызова [tableView endUpdates].

Кто-нибудь получил какие-либо советы по отладке, указатели или решения? Этот пост в блоге намекает на решение подобной проблемы при создании нового раздела.

У меня есть два варианта, если я не могу решить эту проблему, как:

  1. Откажитесь от анимации в строках и просто вызовите [tableView reloadData]
  2. Переключитесь на использование резервного хранилища NSArray для UITableView и надейтесь на лучшее

Обновление

Я изменил образец приложения Apple «Основные данные» «Местоположения» для непосредственного использования NSFetchedResultsController и воспроизвел проблему. Я загрузил исходный код для этого проекта . Используйте этот проект, чтобы воспроизвести проблему.

  1. Просто нажмите кнопку + несколько раз и подождите
  2. Каждые 5 секунд предмет перемещается в другую категорию.
  3. Когда первая категория собирается опустошиться, она падает.

Иногда сбой заключается в создании двух анимаций для одной и той же ячейки, как в других вопросах StackOverflow по теме. Чаще всего сбой описан выше и ниже.

Ссылки

Трассировка стека:

#0  0x31e0afbc in objc_msgSend ()
#1  0x32c11522 in -[_UITableViewUpdateSupport(Private) _computeRowUpdates] ()
#2  0x32c10510 in -[_UITableViewUpdateSupport initWithTableView:updateItems:oldRowData:newRowData:oldRowRange:newRowRange:context:] ()
#3  0x32c0f99e in -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:] ()
#4  0x32c0e66c in -[UITableView endUpdatesWithContext:] ()
#5  0x000088d6 in -[DraftHistoryController controllerDidChangeContent:] (self=0x3f6f1e0, _cmd=0x377ca29c, controller=0x3f716e0) at /Users/ataylor/Documents/Documents/Programming/iPhone/Drafter/Drafter/Drafter/DraftHistoryController.m:323
#6  0x3775a892 in -[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] ()

Мои методы NSFetchedResultsControllerDelegate те же, что и в примере кода Apple:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    // The fetch controller is about to start sending change notifications, so prepare the table view for updates.
    [self.tableView beginUpdates];
}


- (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:UITableViewRowAnimationFade];
            break;

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

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

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            // Reloading the section inserts a new row and ensures that titles are updated appropriately.
            [tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
            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:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            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];     // <---- Crash occurs here
}

Соответствующие методы UITableViewControllerDelegate являются следующими:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [[self.fetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

Ответы [ 4 ]

6 голосов
/ 10 ноября 2011

Я быстро взглянул. Ваш измененный проект Locations не потерпел крах на мне, но он генерировал исключения основных данных. Проблема заключается в перезагрузке разделов в контроллере: didChangeObject :. Я изменил код следующим образом, и все выглядит хорошо (включая заголовки разделов) как на iOS 4.3, так и на iOS 5.

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
    UITableView *tableView = self.tableView;

    switch(type) {

        // other cases here

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}
4 голосов
/ 07 ноября 2011

Мне было очень любопытно, что код исправил проблему с добавлением нового раздела, поэтому я решил добавить его вместо моих текущих методов NSFetchedResultsControllerDelegate, и это действительно решило мою проблему.Прослеживая, я обнаружил, что этот код из примера Apple "Locations" падает при использовании NSFetchedResultsController:

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

Но следующий код работает отлично:

    case NSFetchedResultsChangeMove:
        if (newIndexPath != nil) {
            [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath] withRowAnimation: UITableViewRowAnimationTop];
        }
        else {
            [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationFade];
        }
        break;

Я не знаюВ случае, когда заголовки разделов изменятся, я не буду продолжать отладку, чтобы решить проблему, которую Apple намерен исправить вокруг заголовков разделов.

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

Поскольку на каждый вопрос требуется быстрый ответ, вот один (основанный на принятом ответе XJones).Как и в случае с другими ответами, магия заключается в .Move: удалить и вставить вместо перемещения строки.

Это мой делегат NSFRC didChangeObject:

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
    switch type {
    case .Delete:
        if let deletePath = indexPath {
            self.tableView.deleteRowsAtIndexPaths([deletePath], withRowAnimation: .None)
        }
        break
    case .Insert:
        if let insertPath = newIndexPath {
            self.tableView.insertRowsAtIndexPaths([insertPath], withRowAnimation: .Fade)
        }
        break
    case .Update:
        if let updatePath = indexPath {
            self.tableView.reloadRowsAtIndexPaths([updatePath], withRowAnimation: .None)
        }
        break
    case .Move:
        if let indexPath = indexPath, newIndexPath = newIndexPath {
            self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            self.tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
        }
        break
    }
}
0 голосов
/ 25 апреля 2015

Я получал сбой сверхурочно, я удалил последнюю строку в разделе.

Убедитесь, что вы реализовали контроллер: didChangeSection: atIndex: forChangeType: для обработки удаления раздела.

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

    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.viewTable insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                          withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.viewTable deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                          withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...