更改托管对象属性不会触发NSFetchedResultsController更新表视图

nmd*_*ias 25 objective-c nsfetchedresultscontroller nsmanagedobject nsmanagedobjectcontext ios

我有一个带谓词的fetchedResultsController,其中"isOpen == YES"

在调用closeCurrentClockSet时,我将该属性设置为NO.因此,它应该不再出现在我的tableView上.

出于某种原因,这种情况并没有发生.

有人可以帮我解决这个问题吗?

-(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]];

}
Run Code Online (Sandbox Code Playgroud)

-

通过调用自定义fetchRequestForEntity:withPredicate:inManagedObjectContext方法,我有更多方法,使用完全相同的方法.

在这些方法中,更改属性时,tableView会正确更新!但是这一个上面(closeCurrentClockSet),没有!我无法弄清楚为什么.

-

我对fetchedResultsController的实现来自Apple的文档.

另外,另一个细节.如果我将我的应用程序发送到后台.关闭它并重新打开,tableView显示更新应该!

我尽力在stackOverflow上关注之前的问题.没运气.我也NSLogged这个骨头.正确获取对象.这是正确的.isOpen属性正在更新为NO.但由于某种原因,我的fetchedResultsController不更新tableView.

我确实尝试过几个"锤子"解决方案,比如reloadData和调用performFetch.但那没用.或者使用它们会有意义......

编辑:抓住它,DID工作,在我的resultsController上的performFetch之后调用reloadData imediatly但是使用reloadData正在敲定一个解决方案.此外,它还会取消所有动画.我希望我的控制器自动更新我的tableView.

有人可以帮我解决这个问题吗?

任何帮助是极大的赞赏!

谢谢,

努诺

编辑:

完整的实施.

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;

}
Run Code Online (Sandbox Code Playgroud)

-

来自Apple文档的Boilerplate代码:

- (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];
}
Run Code Online (Sandbox Code Playgroud)

第1次更新:

跟踪[managedObjectContext hasChanges]确实返回YES,因为它应该.但是fetchedResultsController不会更新tableView

2ND更新

didChangeObject:atIndexPath:没有为这个特殊情况调用!我有两个方法,使用完全相同的代码,它们恰好是一个不同的实体.而且他们工作得很好.谢谢@Leonardo指出这一点

第3次更新此方法,遵循相同的规则.但确实有效.

- (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];


}
Run Code Online (Sandbox Code Playgroud)

任何人对我可能缺少的东西都有任何其他想法?

谢谢,

努诺

Jod*_*ins 73

好的,我会解释你的问题,然后我会让你判断它是否是FRC中的一个错误.如果您认为这是一个错误,那么您真的应该向苹果提交错误报告.

您的获取结果控制器谓词是这样的:

NSString *predicate = [NSString stringWithFormat: @"clockSet.isOpen == YES"];
Run Code Online (Sandbox Code Playgroud)

这是布尔值的有效谓词.它将遵循clockSet实体的关系并获取其isOpen属性.如果是,YES那么这些对象将被接受到对象数组中.

我想我们很高兴来到这里.

现在,如果您将其中一个clockSet.isOpen属性更改为NO,那么您希望看到该对象从表视图中消失(即,它应该不再与谓词匹配,因此应该从获取的对象数组中删除它).

所以,如果你有这个......

[currentClockSet setIsOpen:[NSNumber numberWithBool:NO]];
Run Code Online (Sandbox Code Playgroud)

然后,无论哪个顶级对象与之关系都currentClockSet应该从你的FRC获取结果数组中"消失".

但是,你没有看到它消失.原因是FRC监控的对象没有改变.是的,谓词密钥路径已更改,但FRC保存实体ClockPairClockSet实际更改的实体.

您可以观看通知,看看幕后发生了什么.

无论如何,FRC将在您执行提取时使用键路径,但它不会监视对其实际的提取对象集中的对象的更改.

最简单的解决方法是"设置"包含此关键路径对象的对象的属性.

例如,我注意到它ClockPair也有一个isOpen属性.如果你有反比关系,那么你可以这样做......

currentClockSet.isOpen = NO;
currentClockSet.clockPair.isOpen = currentClockSet.clockPair.isOpen;
Run Code Online (Sandbox Code Playgroud)

请注意,您根本没有实际更改该值.但是,调用了setter,它触发了KVO,从而触发了私有的DidChange通知,然后告诉FRC对象发生了变化.因此,它重新评估检查以查看是否应包含该对象,找到更改的keypath值,并执行您期望的操作.

因此,如果在FRC谓词中使用键路径,如果更改该值,则需要以蠕虫方式返回FRC阵列中的所有对象并"弄脏它们",以便这些对象位于通知中.传递有关对象的变化.这很难看,但可能比保存或更改获取请求和重新获取更好.

我知道你不相信我,所以继续尝试吧.注意,要使其工作,您必须知道FRC对象数组中的哪些项目会受到更改的影响,并"戳"它们以使FRC注意到更改.

正如我之前提到的,另一个选项是保存上下文并重新获取值.如果您不想保存上下文,则可以在当前上下文中进行提取包括更新,而无需从存储中刷新.

我发现伪造对FRC正在观察的对象的更改是完成对作为其他实体的关键路径的谓词的重新评估的最佳方式.

好吧,那么,这是否是一个错误是有争议的.我个人认为,如果FRC要监控一个关键路径,它应该一直这样做,而不是像我们在这里看到的那样.

我希望这是有道理的,我鼓励你提交一份错误报告.

  • 我从来没有为此感谢你.谢谢你,先生.半年之后,你说的一切现在对我来说都很有意义,虽然当时有些令人困惑.是的,我也是一个很大的丑陋,但如果我没记错的话确实解决了这个问题.谢谢! (10认同)
  • 天!这是你会想“你到底是怎么发现这个的,伙计?!”的答案之一。我向您表示最诚挚的敬意,同时也表示感谢。好一个。 (2认同)

Chr*_*ris 5

你遇到了类似的问题。

我知道这个问题已经很老了,但我希望这对其他人有所帮助:

最简单的方法是引入一个lastUpdated: NSDate在父对象中命名的新属性。

我有一个Conversation包含几个Messages. 每当isRead消息的标志被更新时,我都需要一个ConversationOverviewViewController只显示Conversations的更新。此外,NSFetchedResultsControllerinConversationOverviewVC只获取Conversations 并且对 a 一无所知Message

每当消息更新时,我都会调用message.parentConversation.lastUpdated = NSDate()。这是手动触发更新的一种简单而有用的方法。

希望这可以帮助。