Вопрос: Как заставить мой дочерний контекст видеть изменения, сохраняющиеся в родительском контексте, чтобы они запускали мой NSFetchedResultsController для обновления пользовательского интерфейса?
Вот установка:
У вас есть приложение, которое загружает и добавляет множество XML-данных (около 2 миллионов записей, каждая размером примерно с обычный абзац текста). Файл .sqlite становится примерно 500 МБ в размер. Добавление этого контента в Core Data требует времени, но вы хотите, чтобы пользователь мог использовать приложение, пока данные постепенно загружаются в хранилище данных. Для пользователя должно быть незаметно и незаметно перемещение больших объемов данных, поэтому никаких зависаний, никаких дрожаний: прокрутка как по маслу. Тем не менее, чем больше данных в него добавляется, тем полезнее приложение, поэтому мы не можем вечно ждать, пока данные будут добавлены в хранилище Core Data. В коде это означает, что я действительно хотел бы избежать такого кода в коде импорта:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
Приложение предназначено только для iOS 5, поэтому самым медленным устройством, которое оно должно поддерживать, является iPhone 3GS.
Вот ресурсы, которые я использовал до сих пор для разработки моего текущего решения:
Руководство Apple по программированию основных данных: эффективный импорт данных
iDeveloper TV — производительность основных данных
iDeveloper TV — обновление основных данных для Mac, iPhone и iPad
Импорт и отображение больших наборов данных в Core Data, Маркус Зарра.
У меня есть 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. Я создаю его в основном методе операции и связываю его с основным контекстом.
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
На самом деле это работает очень, ОЧЕНЬ быстро. Просто выполнив эту настройку с тремя контекстами, я смог повысить скорость импорта более чем в 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];
}