Вопрос: Как я могу получить свой дочерний контекст, чтобы видеть, что изменения сохранились в родительском контексте, чтобы они инициировали мой NSFetchedResultsController для обновления пользовательского интерфейса?
Вот настройки:
У вас есть приложение, которое загружает и добавляет большое количество данных XML (около 2 миллионов записей, каждая примерно размером с обычный абзац текста). Размер файла .sqlite составляет около 500 МБ. Добавление этого содержимого в Core Data требует времени, но вы хотите, чтобы пользователь мог использовать приложение, пока данные постепенно загружаются в хранилище данных. Для пользователя должно быть незаметно и незаметно, что большие объемы данных перемещаются, поэтому нет зависаний, нет дрожания: прокрутка как масло. Тем не менее, чем полезнее приложение, тем больше к нему добавляется данных, поэтому мы не можем вечно ждать добавления данных в хранилище Core Data. В коде это означает, что я действительно хотел бы избежать такого кода в коде импорта:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
Приложение работает только на iOS 5, поэтому самое медленное устройство, которое нужно поддерживать, это iPhone 3GS.
Вот ресурсы, которые я использовал для разработки моего текущего решения:
Руководство по программированию основных данных Apple: эффективный импорт данных
- Используйте Autorelease Pools, чтобы сохранить память
- Отношения Стоимость. Импортируйте квартиру, затем исправьте отношения в конце
- Не спрашивайте, можете ли вы помочь, это замедляет процесс O (n ^ 2)
- Импорт в пакетном режиме: сохранение, сброс, слив и повтор
- Отключить менеджер отмены при импорте
iDeveloper TV - Core Data Performance
- Используйте 3 контекста: основной, основной и контекстный типы
iDeveloper TV - обновление основных данных для Mac, iPhone и iPad
- Выполнение сохранений в других очередях с executeBlock ускоряет работу.
- Шифрование замедляет работу, выключите, если можете.
Импорт и отображение больших наборов данных в базовых данных. Автор Marcus Zarra
- Вы можете замедлить импорт, предоставив время текущему циклу выполнения, чтобы все было гладко для пользователя.
- Пример кода доказывает, что можно выполнять большой импорт и поддерживать отзывчивость пользовательского интерфейса, но не так быстро, как при 3 контекстах и асинхронном сохранении на диск.
Мое текущее решение
У меня есть 3 экземпляра NSManagedObjectContext:
masterManagedObjectContext - это контекст, который имеет NSPersistentStoreCoordinator и отвечает за сохранение на диск. Я делаю это так, чтобы мои сохранения были асинхронными и, следовательно, очень быстрыми. Я создаю его при запуске так:
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext - это контекст, который пользовательский интерфейс использует везде. Это дочерний элемент masterManagedObjectContext. Я создаю это так:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
backgroundContext - этот контекст создается в моем подклассе NSOperation, который отвечает за импорт данных XML в Core Data. Я создаю его в методе main операции и связываю его с основным контекстом.
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
Это на самом деле работает очень, очень быстро. Просто выполнив 3 настройки контекста, я смог увеличить скорость импорта более чем в 10 раз! Честно говоря, в это трудно поверить. (Этот базовый дизайн должен быть частью стандартного шаблона базовых данных...)
В процессе импорта я сохраняю 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];
}