Подтвердить что ты не робот

Изменение свойства управляемого объекта не вызывает NSFetchedResultsController для обновления вида таблицы

У меня есть fetchedResultsController с предикатом, где "isOpen == YES"

При вызове closeCurrentClockSet я установил для этого свойства значение НЕТ. Поэтому он больше не должен появляться на моем столе.

Для некоторых причин этого не происходит.

Может кто-нибудь помочь мне понять это, пожалуйста?

-(void)closeCurrentClockSet
{

    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"isOpen == YES"];

    NSArray *fetchedObjects =
        [self fetchRequestForEntity:@"ClockSet"
                      withPredicate:predicate
             inManagedObjectContext:[myAppDelegate managedObjectContext]];

    ClockSet *currentClockSet = (ClockSet *)fetchedObjects.lastObject;

    [currentClockSet setIsOpen:[NSNumber numberWithBool:NO]];

}

-

У меня есть еще несколько методов, используя точный подход, путем вызова настраиваемого метода fetchRequestForEntity: withPredicate: inManagedObjectContext.

В этих методах при изменении свойства tableView получает корректное обновление! Но этот выше (closeCurrentClockSet), нет! Я не могу понять, почему.

-

Моя реализация для моего fetchedResultsController, из документации Apple.

Кроме того, еще одна деталь. Если я отправлю свое приложение на задний план. Закройте его и заново откройте, tableView показывает обновленный как он должен!

Я попытался изо всех сил следить за предыдущими вопросами здесь, в stackOverflow. Не повезло. Я также NSLogged это кость. Объект получает правильную выборку. Это правильный. isOpen Свойство корректно обновляется до НЕТ. Но по какой-то причине мой fetchedResultsController не обновляет tableView.

Я попробовал пару "молотковых" решений, таких как reloadData и call performFetch. Но это не сработало. Или было бы разумно использовать их...

РЕДАКТИРОВАТЬ: поцарапать, что он работает, вызывая reloadData imediatly после выполненияFetch на моем resultsController, но с использованием reloadData забивает решение. Кроме того, он извлекает все анимации. Я хочу, чтобы мой контроллер автоматически обновлял мой tableView.

Может кто-нибудь помочь мне понять это?

Любая помощь очень ценится!

Спасибо,

Нуну

EDIT:

Полная реализация.

fetchedResultsController довольно стандартный и простой. Все остальное связано с документацией Apple

- (NSFetchedResultsController *)fetchedResultsController
{

    if (_fetchedResultsController) {
        return _fetchedResultsController;
    }

    NSManagedObjectContext * managedObjectContext = [myAppDelegate managedObjectContext];

    NSEntityDescription *entity  =
        [NSEntityDescription entityForName:@"ClockPair"
                    inManagedObjectContext:managedObjectContext];

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        [fetchRequest setEntity:entity];

    NSString *predicate = [NSString stringWithFormat: @"clockSet.isOpen == YES"];
        [fetchRequest setPredicate: [NSPredicate predicateWithFormat:predicate]];

    NSSortDescriptor *sortDescriptor1 =
        [[NSSortDescriptor alloc] initWithKey:@"clockIn" ascending:NO];

    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];

        [fetchRequest setSortDescriptors:sortDescriptors];
        [fetchRequest setFetchBatchSize:20];

    NSFetchedResultsController *theFetchedResultsController =
        [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                            managedObjectContext:managedObjectContext
                                              sectionNameKeyPath:nil
                                                       cacheName:@"Root"];


    _fetchedResultsController = theFetchedResultsController;
    _fetchedResultsController.delegate = self;

    return _fetchedResultsController;

}

-

Код котла с документацией 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:UITableViewRowAnimationTop];

            break;

        case NSFetchedResultsChangeDelete:

            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];

            break;

        case NSFetchedResultsChangeUpdate:

            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];

            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationFade];

            break;

        case NSFetchedResultsChangeMove:

            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationLeft];

            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationTop];

            break;
    }
}



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

    UITableView *tableView = self.tableView;

    switch(type) {

        case NSFetchedResultsChangeInsert:

            [tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                     withRowAnimation:UITableViewRowAnimationFade];

            break;

        case NSFetchedResultsChangeDelete:

            [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];
}

1ST ОБНОВЛЕНИЕ:

Отслеживание [managedObjectContext hasChanges] возвращает ДА, как и должно быть. Но fetchedResultsController не обновляет tableView

2ND UPDATE

didChangeObject: atIndexPath: не получает вызов для этого конкретного случая! У меня есть еще два метода, с ТОЧНЫМ кодом, они просто разные. И они отлично работают. Спасибо @Leonardo за указание на это

3-й UPDATE этот метод следует тем же правилам. Но действительно работает.

- (void)clockOut
{
    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"isOpen == %@", [NSNumber numberWithBool:YES]];

    NSArray * fetchedObjects =
        [self fetchRequestForEntity:@"ClockPair"
                      withPredicate:predicate
             inManagedObjectContext:[myAppDelegate managedObjectContext]];

    ClockPair *aClockPair = (ClockPair *)fetchedObjects.lastObject;

    aClockPair.clockOut = [NSDate date];
    aClockPair.isOpen   = [NSNumber numberWithBool:NO];


}

У кого-нибудь есть какие-то другие идеи по поводу того, что я могу потерять?

Спасибо,

Нуну

4b9b3361

Ответ 1

Хорошо, я объясню вашу проблему, тогда я позволю вам судить, является ли это ошибкой в ​​FRC или нет. Если вы считаете, что это ошибка, вы действительно должны подать отчет об ошибке с помощью apple.

Предикат результирующего контроллера результата выглядит следующим образом:

NSString *predicate = [NSString stringWithFormat: @"clockSet.isOpen == YES"];

который является допустимым предикатом для булевого значения. Он будет следовать за отношением объекта clockSet и захватить его атрибут isOpen. Если это YES, то эти объекты будут приняты в массив объектов.

Я думаю, что мы хороши здесь.

Теперь, если вы измените один из атрибутов clockSet.isOpen на NO, то вы ожидаете увидеть, что этот объект исчезнет из представления таблицы (т.е. он больше не будет соответствовать предикату, поэтому его следует удалить из массива извлеченные объекты).

Итак, если у вас есть это...

[currentClockSet setIsOpen:[NSNumber numberWithBool:NO]];

тогда какой бы объект верхнего уровня не имел отношения к currentClockSet, должен "исчезать" из вашего массива FRC из полученных результатов.

Однако вы не видите, что он исчезает. Причина в том, что объект, контролируемый FRC, не изменился. Да, путь ключа предикатов изменился, но FRC содержит объекты ClockPair и объект clockSet, который фактически изменился.

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

В любом случае FRC будет использовать ключевой путь при выполнении выборки, но он не будет отслеживать изменения объектов, которые не находятся в фактическом наборе извлеченных объектов.

Самый простой способ - "установить" атрибут для объекта, который содержит этот объект пути ключа.

Например, я заметил, что ClockPair также имеет атрибут isOpen. Если у вас есть обратная связь, вы можете сделать это...

currentClockSet.isOpen = NO;
currentClockSet.clockPair.isOpen = currentClockSet.clockPair.isOpen;

Обратите внимание, что вы фактически не изменили значение вообще. Однако был вызван сеттер, который вызвал KVO и, таким образом, частное уведомление DidChange, которое затем сообщило FRC, что объект изменился. Таким образом, он переоценивает проверку, чтобы увидеть, должен ли объект быть включен, находит значение keypath изменено и делает то, что вы ожидаете.

Итак, если вы используете ключевой путь в своем предикате FRC, если вы измените это значение, вам нужно червь вернуться ко всем объектам в массиве FRC и "загрязнить их", чтобы эти объекты находились в уведомление об изменениях объекта. Это уродливо, но, вероятно, лучше, чем сохранение или изменение запроса на выборку и повторного набора.

Я знаю, что ты мне не веришь, так что продолжай и попробуй. Обратите внимание: для этого вам нужно знать, какие элементы в массиве FRC объектов будут затронуты изменением, и "вытолкнуть" их, чтобы заставить FRC заметить изменения.

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

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

ОК, так, будь то ошибка или нет, для некоторых обсуждений. Лично я думаю, что если FRC собирается контролировать ключевой путь, он должен делать это полностью, и не так, как мы видим здесь.

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

Ответ 2

После [currentClockSet setIsOpen:[NSNumber numberWithBool:NO]]; вы можете сохранить контекст управляемого объекта:

NSError *saveError = nil;
if( ![[myAppDelegate managedObjectContext] save:&saveError] ) {
    // handle error saving context
}

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

Ответ 3

У вас возникла аналогичная проблема.

Я знаю, что этот вопрос довольно старый, но я надеюсь, что это поможет кому-то еще:

Самый простой способ - ввести в родительский объект новое свойство с именем lastUpdated: NSDate.

У меня был Conversation, который содержит несколько Messages. Всякий раз, когда флаг isRead сообщения был обновлен, мне понадобилось обновление в ConversationOverviewViewController, которое отображает только Conversation s. Кроме того, NSFetchedResultsController в ConversationOverviewVC выводит только Conversation и ничего не знает о Message.

Всякий раз, когда сообщение обновлялось, я вызывал message.parentConversation.lastUpdated = NSDate(). Это простой и удобный способ запуска обновления вручную.

Надеюсь, что это поможет.