Реализация быстрого и эффективного импорта основных данных в iOS 5

Вопрос: Как заставить мой дочерний контекст видеть изменения, сохраняющиеся в родительском контексте, чтобы они запускали мой 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 контекстов: основной, основной и ограничивающий типы контекста

iDeveloper TV — обновление основных данных для Mac, iPhone и iPad

  • Запуск сохранений в других очередях с помощью PerformBlock ускоряет работу.
  • Шифрование замедляет работу, отключите его, если можете.

Импорт и отображение больших наборов данных в Core Data, Маркус Зарра.

  • Вы можете замедлить импорт, дав время текущему циклу выполнения, чтобы все было гладко для пользователя.
  • Пример кода доказывает, что можно выполнять большой импорт и поддерживать отзывчивость пользовательского интерфейса, но не так быстро, как с 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. Я создаю его в основном методе операции и связываю его с основным контекстом.

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];
}

98
задан Cœur 19 May 2019 в 05:09
поделиться