Добавление уникальных объектов в Core Data

Я работаю над приложением для iPhone, которое получает ряд объектов из базы данных. Я хотел бы сохранить их с помощью Core Data, но у меня проблемы с отношениями.

Деталь содержит любое количество POI (достопримечательностей). Когда я получаю набор POI с сервера, они содержат идентификатор детали. Чтобы связать POI с деталью (по идентификатору), мой процесс выглядит следующим образом: запросить ManagedObjectContext для detailID. Если такая деталь существует, добавьте к ней пои. В противном случае создайте деталь (у нее есть другие свойства, которые будут заполняться лениво).

Проблема в производительности. Выполнение постоянных запросов к Core Data выполняется медленно, до такой степени, что добавление списка из 150 POI занимает минуту благодаря множеству задействованных взаимосвязей.

В моей старой модели до Core Data (различные объекты кеш-памяти NSDictionary) этот процесс был очень быстрым (найдите ключ в словаре, а затем создайте его, если он не существует)

У меня больше отношений, чем только это, но почти каждый должен пройти эту проверку (некоторых много или много, и у них есть настоящая проблема).

Есть ли у кого-нибудь предложения, как я могу с этим помочь? Я мог бы выполнять меньше запросов (путем поиска нескольких разных идентификаторов), но я не уверен, насколько это поможет.

Некоторый код:

        POI *poi = [NSEntityDescription
                insertNewObjectForEntityForName:@"POI"
                inManagedObjectContext:[(AppDelegate*)[UIApplication sharedApplication].delegate managedObjectContext]];

    poi.POIid = [attributeDict objectForKey:kAttributeID];
    poi.detailId = [attributeDict objectForKey:kAttributeDetailID];
    Detail *detail = [self findDetailForID:poi.POIid];
    if(detail == nil)
    {
        detail = [NSEntityDescription
                    insertNewObjectForEntityForName:@"Detail"
                    inManagedObjectContext:[(AppDelegate*)[UIApplication sharedApplication].delegate managedObjectContext]];
        detail.title = poi.POIid;
        detail.subtitle = @"";
        detail.detailType = [attributeDict objectForKey:kAttributeType];
    }





-(Detail*)findDetailForID:(NSString*)detailID {
NSManagedObjectContext *moc = [[UIApplication sharedApplication].delegate managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription
                                          entityForName:@"Detail" inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];

NSPredicate *predicate = [NSPredicate predicateWithFormat:
                          @"detailid == %@", detailID];
[request setPredicate:predicate];
NSLog(@"%@", [predicate description]);

NSError *error;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array == nil || [array count] != 1)
{
        // Deal with error...
    return nil;
}
return [array objectAtIndex:0];
}

person Dimitri    schedule 05.08.2009    source источник
comment
Объясните свои объекты более подробно и опубликуйте часть кода, который вы используете. Почти 100% вероятность, что есть способ делать то, что вы уже делаете, намного быстрее.   -  person Sneakyness    schedule 06.08.2009


Ответы (3)


На этой странице представлена ​​некоторая помощь по оптимизации производительности: http://developer.apple.com/documentation/Cocoa/Conceptual/CoreData/Articles/cdPerformance.html#//apple_ref/doc/uid/TP40003468-SW1.

Хотя это и не очень эффективно, почему бы просто не создать их в памяти с помощью NSDictionary? Прочтите все из Core Data в NSDictionary, затем объедините свои данные, заменив все в Core Data.

person Norman    schedule 05.08.2009
comment
Интересная идея. Таким образом, при создании объектов шаблон будет выглядеть следующим образом: 1. Извлечь возможный диапазон связанных объектов, сохранить в NSDictionary с ключом 2. Если идентификатор существует, добавить связь с этим объектом 3. Если идентификатор отсутствует, добавьте новый объект. как в словарь, так и в Core Data. Добавьте отношения. - person Dimitri; 06.08.2009

Ознакомьтесь с разделом «Пакетный сбой» на странице «Производительность основных данных» в Руководстве по программированию основных данных Xcode, на которое Норман ссылался в своем ответе.

Получение только тех управляемых объектов, идентификаторы которых являются IN коллекцией (NSSet, NSArray, NSDictionary) идентификаторов объектов, возвращаемых сервером, может быть еще более эффективным.

NSSet *oids = [[NSSet alloc] initWithObjects:@"oid1", @"oid2", ..., nil];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"oid IN %@", oids];
[oids release];

ОБНОВЛЕНИЕ: я использовал этот совет в решении для acani usersView. Обычно после загрузки ответа пользователей в формате JSON iPhone использует популярная платформа JSON с открытым исходным кодом для разбора ответа на NSArray из NSDictionary объектов, каждый из которых представляет пользователя. Затем он делает NSArray их uid и выполняет пакетную выборку Core Data, чтобы узнать, существует ли какой-либо из них уже на iPhone. Если нет, вставляет. Если это так, он обновляет существующие, только если их атрибут updated старше, чем атрибут с сервера.

person ma11hew28    schedule 23.10.2010

У меня все это действительно хорошо работает, благодаря Норману, который направил меня на правильный путь. Я отправлю сюда свой вспомогательный класс для других.

По сути, мой вспомогательный класс будет искать, существует ли NSManagedObject для некоторого идентификатора, и может создать его для некоторого идентификатора. Это выполняется для меня достаточно быстро: 1000 операций поиска / создания на моем iPhone занимают около 2 секунд (я также сделал несколько других вещей, чистый поиск / создание, вероятно, будет быстрее).

Он делает это путем кэширования словаря всех NSManagedObjects и проверки этого кеша, а не выполнения нового NSFetchRequest.

Пара модификаций, которые могут помочь еще больше ускорить процесс: 1. Получить только выбранные свойства для NSManagedObjects 2. Получить в словарь только свойство идентификатора для NSManagedObject, а не весь объект. В моем тестировании производительности одиночный запрос не был медленной частью (но с 1000 элементами, я ожидал, что он будет быстрым). Самой медленной частью было создание предметов.

  #import "CoreDataUniquer.h"


@implementation CoreDataUniquer

    //the identifying property is the field on the NSManagedObject that will be used to look up our custom identifier
-(id)initWithEntityName:(NSString*)newEntityName andIdentifyingProperty:(NSString*)newIdProp
{
    self = [super init];
    if (self != nil) {
        entityName = [newEntityName retain];
        identifyingProperty = [newIdProp retain];
    }
    return self;
}

-(NSManagedObject*)findObjectForID:(NSString*)identifier
{
    if(identifier == nil)
    {
        return nil;
    }
    if(!objectList)
    {   
        NSManagedObjectContext *moc = [(AppDelegate*)[UIApplication sharedApplication].delegate managedObjectContext];
        NSEntityDescription *entityDescription = [NSEntityDescription
                                                  entityForName:entityName inManagedObjectContext:moc];
        NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
        [request setEntity:entityDescription];

        NSError *error;
        NSArray *array = [moc executeFetchRequest:request error:&error];
        objectList = [[NSMutableDictionary dictionary] retain];
        for (NSManagedObject* p in array) {
            NSString* itemId = [p valueForKey:identifyingProperty];
            [objectList setObject:p forKey:itemId];
        }
    }
    NSManagedObject* returnedObject = [objectList objectForKey:identifier];
    return returnedObject;
}
-(NSManagedObject*)createObjectForID:(NSString*)identifier
{

    NSManagedObject* returnedObject = [NSEntityDescription
                                       insertNewObjectForEntityForName:entityName
                                       inManagedObjectContext:[(AppDelegate*)[UIApplication sharedApplication].delegate managedObjectContext]];
    [returnedObject setValue:identifier forKey:identifyingProperty];
    [objectList setObject:returnedObject forKey:identifier];
    return returnedObject;
}


- (void) dealloc
{
    DESTROY(entityName);
    DESTROY(identifyingProperty);
    [super dealloc];
}

@end
person Dimitri    schedule 14.08.2009
comment
Кроме того, убедитесь, что вы отметили опцию индекса в свойстве идентификатора. - person Dimitri; 27.05.2010
comment
Спасибо! Не могли бы вы также выложить .h файл? - person ma11hew28; 23.10.2010
comment
Вы пытаетесь найти объект со значением атрибута, проверяя все объекты из магазина. Вы получаете доступ ко всем этим элементам, и это может изменить состояние ошибки на реальный запрос. Я думаю, вы должны использовать предикат и ограничение для NSFetchRequest и выполнить запрос. если count> 0, это означает, что объект с атрибутом уже существует в постоянном хранилище. После этого вы можете получить доступ к этому результату запроса, чтобы обновить свою сущность. - person Valerii Pavlov; 27.06.2012