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

时间:2012-09-11 13:11:56

标签: objective-c ios nsfetchedresultscontroller nsmanagedobject nsmanagedobjectcontext

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

在调用 closeCurrentClockSet 时,我将该属性设置为。因此,它不应再出现在我的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]];

}

-

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

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

-

我的fetchedResultsController的实现来自Apple的文档。

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

我已尽力在stackOverflow上关注之前的问题。没运气。我也NSLogged这个骨头。 正确获取对象。这是正确的。 isOpen Property 正在正确更新为。但由于某种原因,我的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;

}

-

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

第1次更新:

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

2ND UPDATE

对于这种特殊情况,

didChangeObject:atIndexPath:不会被调用! 我有两个方法,使用完全相同的代码,它们恰好是一个不同的实体。而且他们工作得很好。谢谢@Leonardo指出这个

3TH 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];


}

对于我可能遗失的内容,任何人都有其他想法吗?

谢谢,

努诺

3 个答案:

答案 0 :(得分:70)

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

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

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

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

我认为我们很高兴来到这里。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

好吧,那么,这是否是一个错误是一些争论的焦点。就个人而言,我认为如果FRC要监视一个关键路径,它应该一直这样做,而不是像我们在这里看到的那样。

我希望有道理,我鼓励你提交错误报告。

答案 1 :(得分:3)

你遇到了类似的问题。

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

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

我有Conversation,其中包含多个Messages。每当消息的isRead标志更新时,我都需要ConversationOverviewViewController中仅显示Conversation的更新。此外,NSFetchedResultsController中的ConversationOverviewVC仅提取Conversation,并且对Message一无所知。

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

希望这有帮助。

答案 2 :(得分:0)

[currentClockSet setIsOpen:[NSNumber numberWithBool:NO]];之后可以保存托管对象上下文:

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

我怀疑保存上下文后您的UITableView会正确更新。这可能是您将应用程序发送到后台的原因。我怀疑你的核心数据堆栈是在应用程序的委托中设置的,当它进入后台时它会在主NSManagedObjectContext上执行保存。