Вопрос : Как мне заставить мой дочерний контекст видеть изменения, сохраняющиеся в родительском контексте, чтобы они запускали мой NSFetchedResultsController для обновления пользовательского интерфейса?
Вот установка:
У вас есть приложение, которое загружает и добавляет много XML-данных (около 2 миллионов записей, каждая примерно размером с обычный абзац текста) .sqlite-файл становится размером около 500 МБ. Добавление этого контента в Core Data требует времени, но вы хотите, чтобы пользователь мог использовать приложение, пока данные загружаются в хранилище данных постепенно. Перемещение больших объемов данных должно быть невидимым и незаметным для пользователя, чтобы не было зависаний и дрожания: прокручивается как масло. Тем не менее, приложение тем полезнее, чем больше в него добавляется данных, поэтому мы не можем вечно ждать, пока данные будут добавлены в хранилище Core Data. В коде это означает, что я действительно хотел бы избежать кода, подобного этому, в коде импорта:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
Приложение поддерживает только iOS 5, поэтому самое медленное устройство, которое оно должно поддерживать, - это iPhone 3GS.
Вот ресурсы, которые я использовал до сих пор для разработки моего текущего решения:
Руководство Apple по программированию основных данных: эффективный импорт данных
- Используйте пулы автозапуска, чтобы уменьшить объем памяти
- Стоимость отношений. Импортируйте плоский, а в конце исправляйте отношения
- Не спрашивайте, можете ли вы помочь, это замедляет работу на O (n ^ 2)
- Импорт пакетами: сохранение, сброс, слив и повтор
- Отключить диспетчер отмены при импорте
iDeveloper TV - Производительность основных данных
- Используйте 3 контекста: типы контекста Master, Main и Confinement
iDeveloper TV - Обновление Core Data для Mac, iPhone и iPad
- Выполнение сохранений в других очередях с performBlock ускоряет работу.
- Шифрование замедляет работу, отключите его, если можете.
Маркус Зарра, Импорт и отображение больших наборов данных в основных данных
- Вы можете замедлить импорт, дав время текущему циклу выполнения, чтобы пользователь чувствовал себя комфортно.
- Пример кода доказывает, что можно выполнять большой импорт и поддерживать отзывчивость пользовательского интерфейса, но не так быстро, как с 3 контекстами и асинхронным сохранением на диск.
Мое текущее решение
У меня есть 3 экземпляра NSManagedObjectContext:
masterManagedObjectContext - это контекст, который имеет NSPersistentStoreCoordinator и отвечает за сохранение на диск. Я делаю так, чтобы мои сохранения были асинхронными и, следовательно, очень быстрыми. Я создаю его при запуске так:
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext - это контекст, который UI использует везде. Это дочерний элемент masterManagedObjectContext. Я создаю это так:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
backgroundContext - этот контекст создается в моем подклассе NSOperation, который отвечает за импорт данных XML в Core Data. Я создаю его в основном методе операции и связываю там с главным контекстом.
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
На самом деле это работает очень, ОЧЕНЬ быстро. Просто выполнив эти 3 настройки контекста, я смог увеличить скорость импорта более чем в 10 раз! Честно говоря, в это трудно поверить. (Этот базовый дизайн должен быть частью стандартного шаблона Core Data ...)
В процессе импорта я сохраняю 2 разными способами. Каждые 1000 элементов, которые я сохраняю в фоновом контексте:
BOOL saveSuccess = [backgroundContext save:&error];
Затем в конце процесса импорта я сохраняю основной / родительский контекст, который якобы выталкивает изменения в другие дочерние контексты, включая основной контекст:
[masterManagedObjectContext performBlock:^{
NSError *parentContextError = nil;
BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
Проблема : проблема в том, что мой пользовательский интерфейс не обновится, пока я не перезагружу представление.
У меня есть простой UIViewController с UITableView, в который загружаются данные с помощью NSFetchedResultsController. Когда процесс импорта завершается, NSFetchedResultsController не видит никаких изменений из родительского / основного контекста, и поэтому пользовательский интерфейс не обновляется автоматически, как я привык видеть. Если я вытащу UIViewController из стека и снова загрузю его, все данные будут там.
Вопрос : Как мне заставить мой дочерний контекст видеть изменения, сохраняющиеся в родительском контексте, чтобы они запускали мой NSFetchedResultsController для обновления пользовательского интерфейса?
Я пробовал следующее, что просто зависает в приложении:
- (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];
}