Почему NSFetchedResultsController не обновляется новыми данными? - PullRequest
6 голосов
/ 25 февраля 2012

Моя базовая модель данных имеет две сущности: Author и Book с отношением «ко-многим» (один автор -> много книг).В главном окне я отображаю список книг, где каждая ячейка содержит название книги и имя автора.Представление также разделено на разделы, где каждый заголовок раздела является именем автора.(обратите внимание, что «author.name» установлено как для дескриптора сортировки, так и для sectionNameKeyPath)

Вот код (упрощенный для ясности):

- (NSFetchedResultsController *)fetchedResultsController {

    if (__fetchedResultsController != nil) {
        return __fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Book" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"author.name" ascending:YES] autorelease];
    NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];

    [fetchRequest setSortDescriptors:sortDescriptors];

    NSFetchedResultsController *aFetchedResultsController = [[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"author.name" cacheName:nil] autorelease];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

    NSError *error = nil;
    [self.fetchedResultsController performFetch:&error];

    return __fetchedResultsController;
}    

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }

     Book* book = [self.fetchedResultsController objectAtIndexPath:indexPath];
     cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", book.name, book.author.name];
    return cell;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

    return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController*)controller {

    [self.tableView reloadData];
}

Теперь, если пользователь меняет автораимя и затем возвращается к основному виду, ячейки и разделы будут отображать старое имя автора.После поиска в Интернете я обнаружил следующий код, который устраняет проблему со старым именем автора в ячейках, но не в заголовках разделов:

- (void)saveAuthorName:(NSString *)newName {
    for (Book* book in author.books) {
        [book willChangeValueForKey:@"author"];
    }

    author.name = newName;

    for (Book* book in author.books) {
        [book didChangeValueForKey:@"author"];
    }

    // save changes
    NSError * error ;
    if( ![self.moc save:&error] ) {
        // Handle error
    } 
}

Почему [self.fetchedResultsController sections] все еще содержит старые имена авторов?Пожалуйста, помогите!

Обновление # 1

Этот раздел относится к Ответу № 1 Маркуса

Хммм, все еще немного нечетко.Вы говорите, что количество разделов неверно?

Количество разделов не изменилось.Содержимое объектов в массиве свойств Sections является неправильным.

На основании опубликованного кода вы просто извлекаете экземпляры NSManagedObject из NSFetchedResultsController.Возможно, есть некоторая путаница относительно того, что это такое?

В коде я извлекаю экземпляры NSManagedObject из NSFetchedResultsController, чтобы отобразить название книги и имя автора для каждой ячейки таблицы(когда вызывается cellForRowAtIndexPath).Однако заголовки каждого раздела в UITableView не взяты из NSManagedObject, насколько я понимаю, а взяты из _NSDefaultSectionInfo объекта, который реализует протокол NSFetchedResultsSectionInfo (когда вызывается titleForHeaderInSection).

Я понял это с помощью следующего кода, который я написал для отладки:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

    id mySection = [[fetchedBooks sections] objectAtIndex:section];
    NSLog(@"%@", mySection)

    return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}

Результатом журнала было <_NSDefaultSectionInfo: 0x8462b90>.Документация NSFetchedResultsController для свойства Sections показывает:

/* Returns an array of objects that implement the NSFetchedResultsSectionInfo protocol.
   It's expected that developers use the returned array when implementing the following methods of the UITableViewDataSource protocol

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; 
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section;
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; 

*/

Поэтому, пожалуйста, исправьте меня, если я ошибаюсь: _NSDefaultSectionInfo не является NSManagedObject, верно?Если да, то как NSFetchedResultsController может обнаруживать изменения для _NSDefaultSectionInfo объектов при изменении Автор NSManagedObject?

Так что это приводит к паре вопросов:

Как выизменение имени автора в этом другом виде?

Код для изменения имени автора написан выше в saveAuthorName.Последовательность действий в приложении выглядит следующим образом:

  • В основной UITableView выберите ячейку книги, открывающую новый вид книги с помощью контроллера навигации.

  • В представлении книги выберите автора выбора, который открывает новое представление выбора автора с помощью контроллера навигации.(все авторы перечислены в UITableView)

  • В представлении выбора автора выберите любого автора, открывающего новое представление редактирования автора с помощью контроллера навигации.

  • В представлении «Редактирование-Автор» измените имя автора и сохраните его, чтобы закрыть представление и перенести предыдущее представление («Выбор-Автор») в стек контроллера навигации.

  • Теперь возможновыберите другого автора и отредактируйте его и т. д. до закрытия этого представления.(Приносит вид книги)

  • Вид закрытия книги, выводит на основной вид, где отображаются все книги.

Имя автора в ячейке старое или просто заголовок раздела?

Ячейка прекрасно обновлена ​​с именем автора (благодаря willChangeValueForKey и didChangeValueForKey, названным в saveAuthorName).Только заголовок раздела старый.

Как выглядят ваши методы делегата?

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

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • controllerDidChangeContent

Требуется ли какой-либо другой метод?

Вы уверены, что ваш - [UITableViewDatasource tableView: titleForHeaderInSection:] срабатывает после того, как вы вернетесь из редактирования?

100% уверен.titleForHeaderInSection возвращает старые значения и вызывается после сохранения изменений.(cellForRowAtIndexPath также вызывается после сохранения изменений, но приносит новые значения)

Какие методы NSFetchedResultsControllerDelegate запускаются при возврате?

Если вы имеете в виду после сохранения (означает, что после вызова saveAuthorName будут вызваны следующие методы:

  • controllerWillChangeContent: (не используется, только для отладочной информации)

  • controller:didChangeObject: (не используется, только для отладочной информации)

  • controllerDidChangeContent:

Если вы имеете в виду по возвращении к основному виду (означает закрытие вида Книги) следующие вызываемые методы:

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • numberOfSectionsInTableView

  • numberOfRowsInSection

Я ценю вашу помощь.Спасибо!

Обновление # 2

Вы реализуете -controller: didChangeSection: atIndex: forChangeType :?

Да, но не меняется при изменении имени автора.Текущая конфигурация для NSFetchedResultsController выглядит следующим образом:

  • Объект: Книга
  • Дескриптор сортировки: author.name
  • sectionNameKeyPath: author.name

Изменение названия книги (а не имени автора) вызовет событие didChangeSection, когда NSFetchedResultsController настроен следующим образом:

  • Объект: Книга
  • СортировкаДескриптор: name
  • sectionNameKeyPath: name

Это означает, что делегат правильно подключен к NSFetchedResultsController.

Это выглядит как вызов [book willChangeValueForKey:@"author"] и [book didChangeValueForKey:@"author"]при изменении имени автора недостаточно для NSFetchedResultsController для отслеживания изменений раздела.

Ответы [ 3 ]

5 голосов
/ 27 февраля 2012

Как правило, вам не нужно сохранять изменения, если вы имеете дело с одним NSManagedObjectContext как для NSFetchedResultsController, так и для UIViewController, который вносит изменения.

Это не относится, если у вас более одного NSManagedObjectContext.

Если у вас есть один NSManagedObjectContext, я бы позаботился о том, чтобы у вас был установлен делегат на NSFetchedResultsController, и поставил точки останова в методах делегатов, чтобы увидеть, получаете ли вы какие-либо обратные вызовы.

Я бы также использовал отладчик и распечатал указатели для NSManagedObject (s), с которыми вы работаете, и убедился бы, что они совпадают. Если нет, то это указывает на проблему с NSManagedObjectContext.

Ответ № 1

Хммм, все еще немного нечетко. Вы говорите, что число разделов неверно?

На основании опубликованного кода вы просто извлекаете NSManagedObject экземпляров из NSFetchedResultsController. Возможно, есть некоторая путаница относительно того, что это такое?

NSFetchedResultsController - это просто контейнер, имеющий один или несколько разделов. Эти разделы также являются просто контейнером, который содержит один или несколько экземпляров NSManagedObject. Это те же самые экземпляры NSManagedObject, к которым вы могли бы получить доступ в любом другом месте вашего приложения (при условии единого NSManagedObjectContext дизайна).

Поэтому, если вы измените данные в NSManagedObject в любом месте в вашем приложении, они будут обновлены в NSFetchedResultsController, потому что это тот же объект, не копия, а точно такой же объект.

Итак, это приводит к паре вопросов:

  1. Как вы меняете имя автора в этом другом представлении?
  2. Имя автора в старой ячейке или просто заголовок раздела?
  3. Как выглядят ваши методы делегата?
  4. Вы уверены, что ваш -[UITableViewDatasource tableView: titleForHeaderInSection:] стреляет после того, как вы вернулись из редактирования?
  5. Какие NSFetchedResultsControllerDelegate методы стреляют по возвращении?

Ответ № 2

Реализуете ли вы -controller: didChangeSection: atIndex: forChangeType:? Если нет, пожалуйста, сделайте это и скажите мне, если это срабатывает. Если оно срабатывает, то когда оно срабатывает? До или после звонка на -[UITableViewDatasource tableView: titleForHeaderInSection:]?

Ответ № 3

Это начинает звучать как ошибка Apple.

Пара мыслей:

  1. Что произойдет, если вы выполните -performFetch: после того, как автор будет отмечен? Интересно, сработает ли это?
  2. Я настоятельно предлагаю вам создать тестовый пример для этого. Затем вы можете отправить его в Apple на радар ( vital ), и я могу поиграть с тестом и посмотреть, есть ли чистое решение.
1 голос
/ 04 апреля 2017

Сегодня со Swift все проще.

  1. Вы внедрили NSFetchedResultsControllerDelegate в свой контроллер
  2. Установите свой контроллер как делегат вашего NSFetchedResultsController.
  3. Не используйте performFetch на вашем fetchedResultsController, это поглощает событие, которое обычно отправляется делегату.
  4. Изменение управляемого объекта -> должны вызываться методы делегата.
0 голосов
/ 26 февраля 2012

Почему вы не фиксируете транзакции, сохраняя изменения?

- (void)saveAuthorName:(NSString *)newName {
    for (Book* book in author.books) {
        [book willChangeValueForKey:@"author"];
    }

    author.name = newName;

    for (Book* book in author.books) {
        [book didChangeValueForKey:@"author"];
    }
    NSError * error ;
    if( ![self.moc save:&error] ) {
         ..... do something ..
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...