objective-c – NSFetchedResultsController更新和一对一关系的问题

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

我可以使用一个基于XCode在基于导航的CoreData应用程序中生成的标准模板项目的简单示例来重新创建它.该模板创建一个具有timeStamp属性的Event实体.我想为这个事件添加一个新的实体“Tag”,它与Entity只是一对一的关系,这个想法是每个事件都有一些标签列表中的特定标签.我在Core Data编辑器中创建了从Event到Tag的关系,以及从Tag到Event的反向关系.然后我为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;

然后我在发布时用一些数据预填充了Tags实体,这样我们就可以在插入新事件时从Tag中选择.在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代码,将Tag添加到我们正在插入的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 *)控制器didChangeObject …],一次用于插入,一次用于更新.我不确定为什么要调用更新.这是关键:如果我删除Event和Tag之间的反向关系,插入开始工作就好了!只调用插入,行不重复,并且工作正常.

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

谢谢.

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

希望这可以帮助!

点赞