У меня возникли проблемы со вставками, использующими NSFetchedResultsController с простым отношением «один к одному».Когда я создаю новый объект Source, который имеет отношение «один к одному» с объектом Target, он, кажется, вызывает - контроллер ((void): (NSFetchedResultsController *) controller didChangeObject ...] дважды с обоими типами NSFetchedResultsChangeInsert и NSFetchedResultsChangeUpdate,что приводит к тому, что представление таблицы отображает неточные данные сразу после обновления.
Я могу воссоздать это на простом примере, основанном на стандартном шаблоне проекта, который XCode генерирует в приложении CoreData на основе навигации.Шаблон создает объект Event с атрибутом timeStamp.Я хочу добавить новую сущность «Tag» к этому событию, которая представляет собой отношение «1: 1» с Entity, идея в том, что каждое событие имеет определенный тег из некоторого списка тегов.Я создаю связь между событием и тегом в редакторе основных данных и обратную связь между тегом и событием.Затем я генерирую подклассы NSManagedObject для Event и Tag, которые довольно стандартны:
@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 некоторыми данными при запуске, чтобы мы могли выбирать из тегапри вставке нового события.В 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();
}
}
Теперь я хочу увидеть табличное представление, отражающее эти изменения, поэтому я сделал 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 ...] дважды, один раз для вставки и один раз для обновления.Я не уверен, ПОЧЕМУ обновление называется хотя.И вот решающий аргумент: если я уберу обратную связь между Event и Tag, вставки начнут работать просто отлично!Вызывается только вставка, строка не дублируется, и все работает хорошо.
Так что же такое с обратной зависимостью, которая вызывает двойной вызов методов делегата NSFetchedResultsController?И я должен просто жить без них в этом случае?Я знаю, что XCode выдает предупреждение, если не указано обратное, и это кажется плохой идеей.Я что-то здесь не так делаю?Это известная проблема с известным обходным решением?
Спасибо.