【问题标题】:Problem with NSFetchedResultsController updates and a to-one relationshipNSFetchedResultsController 更新和一对一关系的问题
【发布时间】:2011-10-03 06:54:29
【问题描述】:

我在使用具有简单一对一关系的 NSFetchedResultsController 进行插入时遇到了一些问题。当我创建一个与目标对象具有一对一关系的新 Source 对象时,它似乎调用 - [(void)controller:(NSFetchedResultsController *)controller didChangeObject ... ] 两次,同时使用 NSFetchedResultsChangeInsert 和 NSFetchedResultsChangeUpdate 类型,这会导致 tableview 在更新后立即显示不准确的数据。

我可以通过一个简单的示例重新创建它,该示例基于 XCode 在基于导航的 CoreData 应用程序中生成的标准模板项目。该模板创建一个带有 timeStamp 属性的 Event 实体。我想为这个事件添加一个新的实体“标签”,它只是与实体的一对一关系,这个想法是每个事件都有一个来自某个标签列表的特定标签。我在 Core Data 编辑器中创建了从事件到标签的关系,以及从标签到事件的反向关系。然后我为 Event 和 Tag 生成 NSManagedObject 子类,它们非常标准:

@interface Event : NSManagedObject {
@private
}
@property (nonatomic, retain) NSDate * timeStamp;
@property (nonatomic, retain) Tag * tag;

and
@interface Tag : NSManagedObject {
@private
}
@property (nonatomic, retain) NSString * tagName;
@property (nonatomic, retain) NSManagedObject * event;

然后我在启动时用一些数据预先填充了标签实体,以便我们可以在插入新事件时从标签中进行选择。在 AppDelegate 中,在返回 persistentStoreCoordinator 之前调用它:

NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Tag" inManagedObjectContext:context];
[fetchRequest setEntity:entity];

NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
//check if Tags haven't already been created. If not, then create them
if (fetchedObjects.count == 0) {
    NSLog(@"create new objects for Tag");

    Tag *newManagedObject1 = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" inManagedObjectContext:context];
    newManagedObject1.tagName = @"Home";

    Tag *newManagedObject2 = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" inManagedObjectContext:context];
    newManagedObject2.tagName = @"Office";

    Tag *newManagedObject3 = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" inManagedObjectContext:context];
    newManagedObject3.tagName = @"Shop";
}

[fetchRequest release];

if (![context save:&error])
{
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}

现在,我更改了 insertNewObject 代码,将标签添加到我们要插入的 Event 属性中。对于这个例子,我只是从 fetchedObjects 列表中选择第一个:

- (void)insertNewObject
{
    NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
    NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
    Event *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
    // If appropriate, configure the new managed object.
    // Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
    [newManagedObject setValue:[NSDate date] forKey:@"timeStamp"];

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entityTag = [NSEntityDescription entityForName:@"Tag" inManagedObjectContext:context];
    [fetchRequest setEntity:entityTag];
    NSError *errorTag = nil;
    NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&errorTag];
    if (fetchedObjects.count > 0) {
        Tag *newtag = [fetchedObjects objectAtIndex:0];
        newManagedObject.tag = newtag;
    }

    // Save the context.
    NSError *error = nil;
    if (![context save:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
}

我现在想看到反映这些更改的 tableview,所以我让 UITableViewCell 输入 UITableViewCellStyleSubtitle 并更改 configureCell 以在详细文本标签中显示 tagName:

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    Event *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = [[managedObject valueForKey:@"timeStamp"] description];
    cell.detailTextLabel.text = managedObject.tag.tagName;
}

现在一切就绪。当我调用 insertNewObject 时,它似乎可以很好地创建第一行,但第二行是第一行的副本,即使时间戳应该相隔几秒钟:

当我上下滚动屏幕时,它会刷新行,然后以正确的时间显示正确的结果。当我单步执行代码时,核心问题出现了:插入新行似乎调用了 [(NSFetchedResultsController *)controller didChangeObject ...] 两次,一次用于插入,一次用于更新。我不确定为什么会调用更新。关键是:如果我删除事件和标签之间的反比关系,插入开始工作就好了!只有插入被调用,行不重复,一切正常。

那么导致 NSFetchedResultsController 委托方法被调用两次的反向关系是什么?在这种情况下,我应该没有他们就生活吗?我知道如果未指定反函数,XCode 会发出警告,这似乎是个坏主意。我在这里做错了吗?这是已知解决方法的一些已知问题吗?

谢谢。

【问题讨论】:

  • 对此有何更新?我正在寻找类似的问题。我收到 1 个上下文更改通知,并且我的获取结果控制器委托被击中两次。

标签: objective-c core-data nsfetchedresultscontroller


【解决方案1】:

关于didChangeObject 被多次调用,我找到了会发生这种情况的一个原因。如果您的控制器中有多个NSFetchedResultsController 共享NSManagedObjectContext,则当数据发生变化时,didChangeObject 将被多次调用。我偶然发现了同样的问题,经过一系列测试,这是我注意到的行为。我还没有测试过如果NSFetchedResultsControllers 不共享NSManagedObjectContext 是否会发生这种行为。不幸的是,didChangeObject 并不能说明是哪个NSFetchedResultsController 触发了更新。为了实现我的目标,我最终在我的代码中使用了一个标志。

希望这会有所帮助!

【讨论】:

    【解决方案2】:

    NSFetchedResultsChangeUpdate 可以使用 [tableView reloadRowsAtIndexPaths:withRowAnimation:] 代替 configureCell 方法。

    case NSFetchedResultsChangeUpdate:
        [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
        break;
    

    【讨论】:

      【解决方案3】:

      我遇到了同样的问题。并有解决办法。 在某些情况下,当在托管对象上下文中调用 -(BOOL)save: 时,NSFetchedResultsController 会被触发两次,直接在插入或操作之后。

      在我的例子中,我正在对 NSManagedObject -(void)willSave 方法中的对象做一些魔术,这会导致NSFetchedResultsController 触发两次。这似乎是一个错误。

      在保存时不要操作插入的对象对我来说是诀窍!

      将上下文保存到稍后的运行循环似乎是另一种解决方案,例如:

      dispatch_async(dispatch_get_main_queue(), ^{ [context save:nil]; });
      

      【讨论】:

        【解决方案4】:

        NSFetchedResultsController 中的对象必须使用永久对象ID 插入。在创建对象之后并保存到持久存储之前,它具有临时对象 ID。保存对象后收到永久对象ID。如果将具有临时 objectID 的对象插入到 NSFetchedResultsController 中,则在保存对象并将其 objectID 更改为永久后,NSFetchedResults 控制器可能会报告插入假重复对象。 实例化将在 NSFetchedResultsController 中获取的对象后的解决方案 - 只需在其 managedObjectContext 上调用 obtainPermanentIDsForObjects

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-23
          • 1970-01-01
          • 2010-11-11
          • 1970-01-01
          相关资源
          最近更新 更多