Вопрос : Как мне заставить мой дочерний контекст видеть изменения, сохраняющиеся в родительском контексте, чтобы они запускали мой 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];
}
Ответы:
Вам, вероятно, следует также постепенно сохранить главный MOC. Нет смысла заставлять этот MOC ждать до конца, чтобы сохранить. У него есть собственный поток, и он также поможет уменьшить объем памяти.
Вы написали:
В вашей конфигурации у вас есть два дочерних элемента (основной MOC и фоновый MOC), оба являются родительскими для «мастера».
Когда вы экономите на дочернем элементе, он передает изменения в родительский. Другие дочерние элементы этого MOC увидят данные в следующий раз, когда они выполнят выборку ... они явно не уведомлены.
Таким образом, при сохранении BG его данные передаются в MASTER. Обратите внимание, однако, что ни одна из этих данных не будет на диске, пока МАСТЕР не сохранит. Более того, любые новые элементы не получат постоянных идентификаторов, пока MASTER не сохранит их на диск.
В вашем сценарии вы загружаете данные в ОСНОВНОЙ MOC путем слияния из МАСТЕР-сохранения во время уведомления DidSave.
Это должно сработать, поэтому мне любопытно, где он «висит». Замечу, что вы не работаете в основном потоке MOC каноническим способом (по крайней мере, не для iOS 5).
Кроме того, вы, вероятно, заинтересованы только в слиянии изменений из главного MOC (хотя ваша регистрация в любом случае выглядит так, как будто она предназначена только для этого). Если бы я использовал уведомление об обновлении при сохранении, я бы сделал это ...
Теперь, что может быть вашей реальной проблемой относительно зависания ... вы показываете два разных вызова для сохранения на главном устройстве. первый хорошо защищен в своем собственном блоке выполнения, а второй - нет (хотя вы можете вызывать saveMasterContext в блоке выполнения ...
Однако я бы также изменил этот код ...
Однако обратите внимание, что MAIN является дочерним элементом MASTER. Таким образом, не должно происходить слияния изменений. Вместо этого просто следите за DidSave на мастере и просто обновите! Данные уже находятся в вашем родителе и просто ждут, когда вы их попросите. Это одно из преимуществ наличия данных в родительском элементе.
Еще одна альтернатива для рассмотрения (и мне было бы интересно услышать о ваших результатах - это много данных) ...
Вместо того, чтобы делать фоновый MOC дочерним по отношению к MASTER, сделайте его дочерним по отношению к MAIN.
Получить это. Каждый раз, когда BG сохраняет, он автоматически помещается в MAIN. Теперь MAIN должен вызвать save, а затем мастер должен вызвать save, но все, что они делают, это перемещает указатели ... пока мастер не сохранит на диск.
Прелесть этого метода в том, что данные поступают из фонового MOC прямо в MOC ваших приложений (затем проходят через них для сохранения).
Существует некоторый штраф за передачу, но вся тяжелая работа выполняется в MASTER, когда он ударяется о диск. И если вы удаляете эти сохранения на мастере с помощью performBlock, тогда основной поток просто отправляет запрос и немедленно возвращается.
Пожалуйста, дайте мне знать, как это происходит!
источник