Базовая структура данных: как эффективно обновлять локальную информацию при изменениях из сети? - PullRequest
5 голосов
/ 05 февраля 2010

В моем приложении есть неэффективность, которую я хотел бы понять и исправить.

Мой алгоритм:

fetch object collection from network
for each object:
  if (corresponding locally stored object not found): -- A
    create object
    if (a nested related object locally not found): -- B
      create a related object

Я выполняю проверку строк A и B, создавая запрос предиката с ключом соответствующего объекта, который является частью моей схемы. Я вижу, что и A (всегда), и B (если выполнение разветвляется на эту часть) генерируют SQL-запрос, например:

2010-02-05 01:57:51.092 app[393:207] CoreData: sql: SELECT <a bunch of fields> FROM ZTABLE1 t0 WHERE  t0.ZID = ? 
2010-02-05 01:57:51.097 app[393:207] CoreData: annotation: sql connection fetch time: 0.0046s
2010-02-05 01:57:51.100 app[393:207] CoreData: annotation: total fetch execution time: 0.0074s for 0 rows.
2010-02-05 01:57:51.125 app[393:207] CoreData: sql: SELECT <a bunch of fields> FROM ZTABLE2 t0 WHERE  t0.ZID = ? 
2010-02-05 01:57:51.129 app[393:207] CoreData: annotation: sql connection fetch time: 0.0040s
2010-02-05 01:57:51.132 app[393:207] CoreData: annotation: total fetch execution time: 0.0071s for 0 rows.

0,0071 с для запроса - это нормально на устройстве 3GS, но если вы добавите 100 из них, вы просто получите блокиратор 700 мс.

В моем коде я использую помощника для выполнения этих выборок:

- (MyObject *) myObjectById:(NSNumber *)myObjectId {
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    [fetchRequest setEntity:[self objectEntity]]; // my entity cache    
    [fetchRequest setPredicate:[self objectPredicateById:objectId]]; // predicate cache    
    NSError *error = nil;
    NSArray *fetchedObjects = [moc executeFetchRequest:fetchRequest error:&error];
    if ([fetchedObjects count] == 1) {
        [fetchRequest release];
        return [fetchedObjects objectAtIndex:0];
    }
    [fetchRequest release];
    return nil;
}

MyObject *obj = [self myObjectById];
if (!obj) {
   // [NSEntityDescription insertNewObjectForEntityForName: ... etc
}

Я чувствую, что это неправильно, и я должен сделать проверку другим способом. Он должен попадать в базу данных только один раз и впоследствии должен быть из памяти, верно? (SQL выполняется даже для тех объектов, которые, я знаю, существуют локально и должны были быть загружены в память с помощью предыдущих запросов.) Но, если у меня есть только myObjectId из внешнего источника, это лучшее, что я мог придумать.

Итак, возможно, вопрос заключается в следующем: если у меня есть myObjectId (свойство Core Data int64 в MyObject), как мне правильно проверить, существует ли соответствующий локальный объект в хранилище компакт-дисков или нет? Предварительно загрузить весь набор возможных совпадений, а затем предсказать локальный массив?

(Одно из возможных решений - переместить это в фоновый поток. Это было бы хорошо, за исключением того, что когда я получаю изменения из потока и выполняю [moc mergeChangesFromContextDidSaveNotification: aNotification]; (получение измененных объектов из фонового потока посредством уведомления ), это все еще блокирует.)

Ответы [ 6 ]

9 голосов
/ 05 февраля 2010

Прочитайте раздел «Внедрение эффективного поиска или создания» в руководстве по программированию основных данных.

По сути, вам нужно создать массив идентификаторов или свойств, таких как имена, или что-либо еще из сущности управляемого объекта.

Затем вам нужно создать предикат, который будет фильтровать управляемые объекты, используя этот массив.

[fetchRequest setPredicate:[NSPredicate predicateWithFormat: @"(objectID IN %@)", objectIDs]];

Конечно, "objectID" может быть чем угодно, что вы можете использовать для идентификации. Это не обязательно NSManagedObjectID.

Затем вы можете сделать один запрос на выборку и выполнить итерацию полученных выборочных объектов, чтобы найти дубликаты. Добавьте новый, если он не существует.

3 голосов
/ 05 февраля 2010

Вы должны сделать одну выборку по всем объектам, но получить только ID сервера для объектов.

Использовать setPropertiesToFetch: с setResultType: установить в NSDictionaryResultType.

3 голосов
/ 05 февраля 2010

Возможно, вы могли бы извлечь урок из почтовых клиентов.

Они работают, сначала запрашивая у сервера список идентификаторов сообщений. Как только у клиента есть этот список, он сравнивает его со своим локальным хранилищем данных, чтобы увидеть, не отличается ли что-либо.

Если есть разница, нужно выполнить одно из нескольких действий. 1. Если он существует на клиенте, но не на сервере И мы IMAP, то удалите локально. 2. Если оно существует на сервере, но не на клиенте, загрузите оставшуюся часть сообщения.

В вашем случае сначала запросите все идентификаторы. Затем отправьте дополнительный запрос, чтобы получить все данные, которых у вас еще нет.

Если у вас есть ситуация, когда запись может существовать локально, но, возможно, была обновлена ​​с момента последней загрузки на сервер, тогда ваш запрос должен включать дату последнего обновления.

1 голос
/ 18 февраля 2010

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

http://henrik.nyh.se/2007/01/importing-legacy-data-into-core-data-with-the-find-or-create-or-delete-pattern

Хотя пример кода не охватывает сетевую часть; вам просто нужно загрузить его в NSDictionary. и затем речь идет о синхронизации локального контекста базовых данных.

1 голос
/ 05 февраля 2010

Похоже, вам нужен NSSet NSManagedObjectIDs, который загружается в память или сохраняется где-то более быстрый доступ, чем ваше хранилище постоянных объектов.

Таким образом, вы можете сравнивать идентификаторы объектов из сети с идентификаторами объектов из вашего кэша, не выполняя запрос на выборку для большого набора данных.

Может быть, добавить идентификатор в кеш из -awakeFromInsert в классах вашего управляемого объекта?

0 голосов
/ 17 апреля 2011

Не ответ, а обновленный URL-адрес к документации «Эффективный импорт данных»

http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html#//apple_ref/doc/uid/TP40003174-SW1

...