更改托管对象属性不会触发NSFetchedResultsController更新表视图 [英] Changing a managed object property doesn't trigger NSFetchedResultsController to update the table view

查看:85
本文介绍了更改托管对象属性不会触发NSFetchedResultsController更新表视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a fetchedResultsController with a predicate, where "isOpen == YES"

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

When calling for closeCurrentClockSet, I set that property to NO. Therefore, it should no longer appear on my tableView.

由于某些原因,这种情况不会发生。

For Some Reason, this is not happening.

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

Can someone help me figure this out please?

-(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 方法,我有更多方法,使用完全相同的方法

I have a couple of methods more, using the exact same approach, by calling a custom fetchRequestForEntity:withPredicate:inManagedObjectContext method.

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

In those methods, when changing a property, tableView get correctly updated! But this one above (closeCurrentClockSet), doesn't! I can't figure out why.

-

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

My implementation for my fetchedResultsController, is from Apple's documentation.

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

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

I have tried my best to follow previous questions here on stackOverflow. No luck. I also NSLogged this to the bone. The object is getting correctly fetched. It is the right one. isOpen Property is being correctly updated to NO. But for some reason, my fetchedResultsController doesn't update tableView.

我尝试了几个锤子解决方案,比如reloadData和调用performFetch。但那没用。或者使用它们是有意义的......

I did try a couple a "hammer" solutions, like reloadData and calling performFetch. But that didn't work. Or would make sense to used them...

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

scratch that, it DID work, calling reloadData imediatly after performFetch on my resultsController but using reloadData is hammering a solution. Plus, it takes out all animations. I want my controller to auto-update my tableView.

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

Can someone help me figure this out?

任何非常感谢帮助!

谢谢,

Nuno

编辑:

完整的实现。

fetchedResultsController非常标准和简单。其他一切都来自Apple的文档

fetchedResultsController is pretty standard and straightforward. Everything else is from Apple's documentation

- (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代码:

--

Boilerplate code from Apple's documentation:

- (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 UPDATE:

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

Tracking [managedObjectContext hasChanges] does return YES, as it should. But fetchedResultsController doesn't update the tableView

2ND UPDATE

didChangeObject:atIndexPath :对于这种特殊情况,不会被称为
我还有2个方法,使用完全相同的代码,它们恰好是一个不同的实体。而且他们工作得很好。感谢@Leonardo指出这一点

didChangeObject:atIndexPath: does not get called for this particular case! I have 2 more methods, with the EXACT same code, they just happen to be a different entity. And they work perfectly. Thank you @Leonardo for pointing this out

3TH UPDATE 此方法遵循相同的规则。但确实有效。

3TH UPDATE this method, follows the same rules. But does actually work.

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


}

任何人对我的任何其他想法可能会丢失?

Anyone has any other ideas for what I might be missing?

谢谢,

Nuno

推荐答案

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

OK, I will explain your problem, then I will let you judge whether it is a bug in FRC or not. If you think it is a bug, then you really should file a bug report with apple.

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

Your fetch result controller predicate is like this:

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

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

which is a valid predicate for a boolean value. It is going to follow the relationship of the clockSet entity and grab its isOpen attribute. If it is YES then those objects will be accepted into the array of objects.

我认为我们很好到这里。

I think we are good up to here.

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

Now, if you change one of clockSet.isOpen attributes to NO, then you expect to see that object disappear from your table view (i.e., it should no longer match the predicate so it should be removed from the array of fetched objects).

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

So, if you have this...

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

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

then, whichever top-level object has a relationship to the currentClockSet should "disappear" from your FRC array of fetched results.

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

However, you do not see it disappear. The reason is that the object monitored by the FRC did not change. Yes, the predicate key path changed, but the FRC holds entities of ClockPair and a ClockSet entity actually changed.

你可以看到通知飞来看看幕后发生了什么。

You can watch the notifications fly around to see what's going on behind the scenes.

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

Anyway, the FRC will use a key path when you do a fetch, but it will not monitor changes to objects that are not in its actual set of fetched objects.

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

The easiest work-around is to "set" an attribute for the object that holds this key path object.

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

For example, I noticed that the ClockPair also has an isOpen attribute. If you have an inverse relationship, then you could do this...

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

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

Notice that you did not actually change the value at all. However, the setter was called, which triggered KVO, and thus the private DidChange notification, which then told the FRC that the object changed. Thus, it re-evaluates the check to see if the object should be included, finds the keypath value changed, and does what you expect.

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

So, if you use a key path in your FRC predicate, if you change that value, you need to worm your way back to all the objects in the FRC array and "dirty them up" so that those objects are in the notification that is passed around about object changes. It's ugly, but probably better than saving or changing your fetch request and refetching.

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

I know you don't believe me, so go ahead and try it. Note, for it to work, you have to know which item(s) in the FRC array of objects would be affected by the change, and "poke" them to get the FRC to notice the change.

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

The other option, as I mentioned earlier, is to save the context, and refetch the values. If you don't want to save the context, you can make the fetch include updates in the current context, without refreshing from the store.

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

I have found that faking a change to an object that the FRC is watching is the best way to accomplish a re-evalution of predicates that are key paths to other entities.

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

OK, so, whether this is a bug or not is up for some debate. Personally, I think if the FRC is going to monitor a keypath, it should do it all the way, and not partially like we see here.

我希望有意义,我鼓励你提交错误报告。

I hope that make sense, and I encourage you to file a bug report.

这篇关于更改托管对象属性不会触发NSFetchedResultsController更新表视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆