iPhone: NSFetchedResultsController с делегатом и обновлением данных из отдельного потока - PullRequest
5 голосов
/ 29 июля 2010

Прежде всего, извините за слишком длинный вопрос.

Я знаю, что здесь есть несколько вопросов, которые обсуждают подобные проблемы, но ни один из этих разговоров о NSFetchedResultsController с делегатом вместе с обновлением в отдельном потоке. И ни одно из решений не помогло мне.
Это существующие вопросы:

Теперь о моей проблеме:

  • У меня есть отдельный поток, который обновляет основные объекты данных из Интернета (используя сокет).
  • Существует немного контроллеров представления, которые отображают данные из одного и того же базового объекта данных (каждая вкладка содержит контроллер представления, отображающий его отфильтрованные данные).
  • Каждый контроллер представления имеет свой собственный экземпляр NSFetchedResultsController, а делегату присваивается значение self.

Иногда я получаю was mutated while being enumerated исключение при обновлении данных в отдельном потоке, а иногда происходит сбой приложения.

Я сделал много манипуляций с кодом, чтобы попытаться это исправить, и кажется, что ничего не помогает.
Я пытался не использовать управляемый объект непосредственно из методов источника данных табличного представления. Вместо этого я создал массив, который содержит список словарей. я заполняю эти словари по методу didChangeObject сверху. Таким образом, я вообще не касаюсь управляемых объектов в контроллере представления.

И тогда я понял, что проблема в NSFetchedResultsController, который, вероятно, итерирует данные все время. И этот объект конфликтует с моим обновлением данных в отдельном потоке.

Вопрос в том, как я могу обновить объекты основных данных в отдельном потоке, как только у меня есть NSFetchedResultsController с делегатом (это означает, что он «наблюдает» за данными и обновляет все время, когда происходит отладка).

NSFetchedResultsControllerDelegate реализация:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
    if ( self.tabBarController.selectedIndex == 0 ) {
        UITableView *tableView = self.tableView;
        @try {
            switch(type) 
            {
                case NSFetchedResultsChangeInsert:
                    [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                    break;
                case NSFetchedResultsChangeDelete:
                    [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                    break;
                case NSFetchedResultsChangeUpdate:
                    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
                    break;
                case NSFetchedResultsChangeMove:
                    [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                    [tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
                    break;
            }
        }
        @catch (NSException * e) {
            NSLog(@"Exception in didChangeObject: %@", e);
        }
    }
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    if ( self.tabBarController.selectedIndex == 0 ) {
        @try {
            switch(type) {
                case NSFetchedResultsChangeInsert:
                    [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                    break;
                case NSFetchedResultsChangeDelete:
                    [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                    break;
            }           
        }
        @catch (NSException * e) {
            NSLog(@"Exception in didChangeSection: %@", e);
        }
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller 
{
    [self.tableView endUpdates];
}

В методах источника данных табличного представления я работаю напрямую с управляемым объектом.

1 Ответ

12 голосов
/ 29 июля 2010

Два отдельных вопроса здесь. Во-первых, если вы получаете мутирующие ошибки, это означает, что вы мутируете набор или массив (или отношение) во время перебора этого набора / массива / отношения. Найди, где ты это делаешь и прекрати это делать. Это единственное решение.

Что касается ваших обновлений. Ваш фон NSManagedObjectContext должен периодически сохраняться. Ваш основной поток должен прослушивать NSManagedObjectContextDidSaveNotification, а когда он получает один, он вызывает основной NSManagedObjectContext в основном потоке (так как уведомление, скорее всего, придет в фоновом потоке) через -mergeChangesFromContextDidSaveNotification: который принимает NSNotification в качестве параметра. Это заставит все ваши NSFetchedResultController экземпляры запускать их методы делегата.

Все просто.

Обновление

Спасибо за ваш ответ. Исключение выдается при обновлении NSManagedObjectContext в фоновом потоке. Я использую один и тот же NSManagedObjectContext в обоих потоках. Приложение должно быть как можно ближе к приложению реального времени - обновления постоянно и таблицы должны обновляться немедленно. Я вообще не сохраняю - я только обновляю NSManagedObjectContext. В одном из упомянутых вопросов я видел, что кто-то использовал для разделения экземпляров NSManagedObjectContext, но он все еще получает те же исключения после объединения изменений. Итак, вы предлагаете использовать 2 отдельных NSManagedObjectContext's?

Сначала прочитайте о многопоточности в Core Data из документации Apple (или моей книги :).

Во-вторых, да, у вас должен быть один контекст для потока, это одно из золотых правил Core Data и многопоточности (другое - не передавать NSManagedObject экземпляры между потоками). Вероятно, это и есть причина вашего сбоя, а если это не так, то это будет источником сбоя в будущем.

Обновление

У меня есть тонны данных, и я обновляю только измененные / новые / удаленные элементы в таблице. Если я начну экономить, то повредит ли это производительности?

Нет, только обновления будут распространяться по всем потокам. Хранилище данных не будет перечитано, поэтому оно будет на улучшать производительность , когда вы разбиваете сохранения на более мелкие куски, потому что вы будете в основном потоке, обновляя пользовательский интерфейс, на меньших кусках, так что пользовательский интерфейс будет казаться лучше.

Однако беспокойство по поводу производительности до завершения приложения - это предварительная оптимизация, которую следует избегать. Гадать, что будет хорошо, а что нет, как правило, плохая идея.

...