Вопрос по nsmanagedobjectcontext, core-data, ios, nsfetchedresultscontroller – Внедрение быстрого и эффективного импорта основных данных на iOS 5

97

Question: Как я могу получить свой дочерний контекст, чтобы увидеть, что изменения сохраняются в родительском контексте, чтобы они инициировали мой NSFetchedResultsController для обновления пользовательского интерфейса?

Here's the setup:

У вас есть приложение, которое загружает и добавляет большое количество данных XML (около 2 миллионов записей, каждая примерно размером с нормальный абзац текста). Размер файла .sqlite составляет около 500 МБ. Добавление этого содержимого в Core Data требует времени, но вы хотите, чтобы пользователь мог использовать приложение, пока данные постепенно загружаются в хранилище данных. Для пользователя должно быть незаметным и незаметным для пользователя то, что большие объемы данных перемещаются, так что никаких зависаний, никаких дрожаний: прокрутка как масло. Тем не менее, чем полезнее приложение, тем больше данных к нему добавляется, поэтому мы не можем ждать вечно, чтобы данные были добавлены в хранилище базовых данных. В коде это означает, что я действительно хотел бы избежать такого кода в коде импорта:

<code>[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
</code>

Приложение работает только на iOS 5, поэтому самое медленное устройство, которое нужно поддерживать, это iPhone 3GS.

Вот ресурсы, которые я использовал для разработки моего текущего решения:

Руководство по программированию основных данных Apple: эффективный импорт данных

Use Autorelease Pools to keep the memory down Relationships Cost. Import flat, then patch up relationships at the end Don't query if you can help it, it slows things down in an O(n^2) manner Import in Batches: save, reset, drain and repeat Turn off the Undo Manager on import

iDeveloper TV - Core Data Performance

Use 3 Contexts: Master, Main and Confinement context types

iDeveloper TV - Базовые данные для Mac, iPhone & amp; Обновление iPad

Running saves on other queues with performBlock makes things fast. Encryption slows things down, turn it off if you can.

Импорт и отображение больших наборов данных в базовых данных. Автор Marcus Zarra

You can slow down the import by giving time to the current run loop, so things feel smooth to the user. Sample Code proves that it is possible to do large imports and keep the UI responsive, but not as fast as with 3 contexts and async saving to disk. My Current Solution

У меня есть 3 экземпляра NSManagedObjectContext:

masterManagedObjectContext - Этот контекст имеет NSPersistentStoreCoordinator и отвечает за сохранение на диск. Я делаю это так, чтобы мои сохранения были асинхронными и поэтому очень быстрыми. Я создаю его при запуске так:

<code>masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
</code>

mainManagedObjectContext - Это контекст, который пользовательский интерфейс использует везде. Это дочерний элемент masterManagedObjectContext. Я создаю это так:

<code>mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
</code>

backgroundContext - Этот контекст создан в моем подклассе NSOperation, который отвечает за импорт данных XML в Core Data. Я создаю его в основном методе операции и связываю его с основным контекстом.

<code>backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
</code>

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

В процессе импорта я сохраняю 2 разных способа. Каждые 1000 предметов я сохраняю в фоновом контексте:

<code>BOOL saveSuccess = [backgroundContext save:&error];
</code>

Затем в конце процесса импорта я сохраняю основной / родительский контекст, который якобы выталкивает изменения в другие дочерние контексты, включая основной контекст:

<code>[masterManagedObjectContext performBlock:^{
   NSError *parentContextError = nil;
   BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
</code>

Problem: Проблема в том, что мой пользовательский интерфейс не будет обновляться, пока я не перезагрузлю представление.

У меня есть простой UIViewController с UITableView, который подается данные с помощью NSFetchedResultsController. Когда процесс импорта завершается, NSFetchedResultsController не видит изменений из родительского / основного контекста, и поэтому пользовательский интерфейс не обновляется автоматически, как я привык видеть. Если я вытолкну UIViewController из стека и загрузлю его снова, все данные будут там.

Question: Как я могу получить свой дочерний контекст, чтобы увидеть, что изменения сохраняются в родительском контексте, чтобы они инициировали мой NSFetchedResultsController для обновления пользовательского интерфейса?

Я пробовал следующее, которое просто зависает приложение:

<code>- (void)saveMasterContext {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];    
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];

    NSError *error = nil;
    BOOL saveSuccess = [masterManagedObjectContext save:&error];

    [notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}

- (void)contextChanged:(NSNotification*)notification
{
    if ([notification object] == mainManagedObjectContext) return;

    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:YES];
        return;
    }

    [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
</code>
Действительно хороший вопрос, и я взял несколько изящных трюков из описания, которое вы предоставили для вашей установки djskinner
См. Apple Docs, связанные с первой из этой статьи. Это объясняет это. Удачи! David Weiss
+1000000 за лучший сформированный, самый подготовленный вопрос. У меня тоже есть ответ ... Потребуется несколько минут, чтобы напечатать его, хотя ... Jody Hagins
Извините, что поднял это после долгого времени. Не могли бы вы уточнить, что значит «импортировать без изменений», а затем в конце исправить отношения? имею в виду? Разве вам еще не нужно хранить эти объекты в памяти, чтобы установить отношения? Я пытаюсь реализовать решение, очень похожее на ваше, и я действительно могу использовать некоторую помощь, чтобы уменьшить объем используемой памяти. Andrea Sprega
Когда вы говорите, что приложение зависло, где оно? Что это делает? Jody Hagins

Ваш Ответ

1   ответ
47

вероятно, сохранить основной MOC также с ходу. Нет смысла в том, чтобы MOC ждал до конца, чтобы сохранить. У него есть свой собственный поток, и он также поможет сохранить память.

Вы написали:

Then at the end of the import process, I save on the master/parent context which, ostensibly, pushes modifications out to the other child contexts including the main context:

В вашей конфигурации у вас есть два дочерних элемента (основной MOC и фоновый MOC), оба являются родителями "master".

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

Таким образом, когда BG сохраняет данные, их данные отправляются в MASTER. Однако обратите внимание, что ни одна из этих данных не будет сохранена на диске до тех пор, пока MASTER не сохранит данные. Более того, любые новые предметы не будут иметь постоянных идентификаторов, пока МАСТЕР не сохранит их на диск.

В вашем сценарии вы извлекаете данные в ОСНОВНОЙ МОК, объединяя их с сохранением MASTER во время уведомления DidSave.

Это должно сработать, поэтому мне любопытно, где он "завис". Отмечу, что вы не работаете в основном потоке MOC каноническим способом (по крайней мере, для iOS 5).

Кроме того, вы, вероятно, заинтересованы только в объединении изменений из основного MOC (хотя ваша регистрация выглядит так, как будто только для этого). Если бы я использовал уведомление об обновлении при сохранении, я бы сделал это ...

- (void)contextChanged:(NSNotification*)notification {
    // Only interested in merging from master into main.
    if ([notification object] != masterManagedObjectContext) return;

    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];

        // NOTE: our MOC should not be updated, but we need to reload the data as well
    }];
}

Теперь, что может быть вашей реальной проблемой в отношении зависания ... вы показываете два разных вызова, чтобы сэкономить на мастере. первый хорошо защищен в своем собственном executeBlock, но второй нет (хотя вы можете вызывать saveMasterContext в executeBlock ...

Однако я также изменил бы этот код ...

- (void)saveMasterContext {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];    
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];

    // Make sure the master runs in it's own thread...
    [masterManagedObjectContext performBlock:^{
        NSError *error = nil;
        BOOL saveSuccess = [masterManagedObjectContext save:&error];
        // Handle error...
        [notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
    }];
}

Тем не менее, обратите внимание, что ГЛАВНАЯ - дитя МАСТЕРА. Так что не стоит объединять изменения. Вместо этого просто наблюдайте за DidSave на мастере и просто заново! Данные уже находятся у вашего родителя, просто ожидая, когда вы их попросите. Это одно из преимуществ наличия данных у родителя в первую очередь.

Рассмотрим другую альтернативу (и мне было бы интересно услышать о ваших результатах - это много данных) ...

Вместо того, чтобы сделать фоновый MOC дочерним элементом MASTER, сделайте его дочерним элементом MAIN.

Взять это. Каждый раз, когда BG сохраняет данные, он автоматически помещается в MAIN. Теперь MAIN должен вызвать save, а затем master должен вызвать save, но все, что он делает, это перемещает указатели ... пока мастер не сохранит на диск.

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

Естьsome штраф за проход, но все тяжелые работы выполняются в MASTER, когда он попадает на диск. И если вы запускаете эти сохранения на мастер с помощью executeBlock, то основной поток просто отправляет запрос и сразу же возвращается.

Пожалуйста, дайте мне знать, как это происходит!

Потрясающие! Это сработало отлично! Тем не менее, я собираюсь попробовать ваше предложение MASTER - & gt; ГЛАВНАЯ - & gt; BG и посмотрим, как это работает, это кажется очень интересной идеей. Спасибо за отличные идеи! David Weiss
Проблема с MASTER - & gt; ГЛАВНАЯ - & gt; Шаблон BG - это когда вы выбираете из контекста BG, он также выбирает из MAIN, что блокирует пользовательский интерфейс и делает ваше приложение не отзывчивым
Обновлен, чтобы изменить executeBlock и Wait для выполнения Block. Не уверен, почему это снова появилось в моей очереди, но когда я прочитал это на этот раз, было очевидно ... не уверен, почему я отпустил это раньше. Да, executeBlockAndWait является повторно входящим. Однако в такой вложенной среде вы не можете вызвать синхронную версию дочернего контекста из родительского контекста. Уведомление может быть (в этом случае отправлено) из родительского контекста, что может привести к взаимоблокировке. Я надеюсь, что это понятно всем, кто придет и прочтет это позже. Спасибо, Дэвид.
Отличный ответ. Я попробую эти идеи сегодня и посмотрю, что я обнаружу. Спасибо! David Weiss
@DavidWeiss Вы пробовали MASTER - & gt; ГЛАВНАЯ - & gt; BG? Я заинтересован в этом шаблоне проектирования и надеюсь узнать, хорошо ли он работает для вас. Спасибо.

Похожие вопросы