我使用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触发了更新.为了实现我的目标,我最终在我的代码中使用了一个标志.
希望这可以帮助!