Внедрение быстрого и эффективного импорта основных данных на iOS 5



Answers

Question

Вопрос : Как получить мой дочерний контекст, чтобы увидеть изменения, сохраняемые в родительском контексте, чтобы они запускали мой NSFetchedResultsController для обновления пользовательского интерфейса?

Вот настройка:

У вас есть приложение, которое загружает и добавляет много XML-данных (около 2 миллионов записей, каждый примерно соответствует размеру обычного абзаца текста). Размер файла .sqlite составляет около 500 МБ. Добавление этого содержимого в основные данные требует времени, но вы хотите, чтобы пользователь мог использовать приложение, пока данные загружаются в хранилище данных постепенно. Он должен быть невидимым и незаметным для пользователя, что вокруг него перемещаются большие объемы данных, поэтому нет зависаний, никаких неудобств: свитки, как масло. Тем не менее, приложение более полезно, чем больше данных добавляется к нему, поэтому мы не можем ждать навсегда, чтобы данные были добавлены в хранилище Core Data. В коде это означает, что мне бы очень хотелось избежать кода, подобного этому в коде импорта:

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];

Приложение - это iOS 5, поэтому самым медленным устройством, которое ему требуется для поддержки, является iPhone 3GS.

Вот ресурсы, которые я использовал до сих пор для разработки моего текущего решения:

Руководство по программированию основных данных Apple: эффективное импортирование данных

  • Используйте пулы Autorelease, чтобы сохранить память
  • Отношения Стоимость. Импортируйте квартиру, затем закрепите отношения в конце
  • Не запрашивайте, если вы можете помочь, это замедляет процесс O (n ^ 2)
  • Импорт в партии: сохранение, сброс, спуск и повтор
  • Отключить Диспетчер отмены при импорте

iDeveloper TV - Производительность основных данных

  • Используйте 3 контекста: типы основного, основного и конфайнмента

iDeveloper TV - Основные данные для Mac, iPhone и iPad

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

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

  • Вы можете замедлить импорт, указав время на текущий цикл цикла, поэтому все будет выглядеть гладко для пользователя.
  • Образец кода доказывает, что можно делать большой импорт и поддерживать пользовательский интерфейс отзывчивым, но не так быстро, как с 3 контекстами и асинхронное сохранение на диск.

Мое настоящее решение

У меня есть 3 экземпляра NSManagedObjectContext:

masterManagedObjectContext - это контекст, который имеет NSPsistentStoreCoordinator и отвечает за сохранение на диск. Я делаю это, поэтому мои сейвы могут быть асинхронными и, следовательно, очень быстрыми. Я создаю его при запуске следующим образом:

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

Это действительно работает очень, очень быстро. Просто выполняя эту 3-х настройку контекста, я смог улучшить скорость своего импорта более чем в 10 раз! Честно говоря, это трудно поверить. (Этот базовый проект должен быть частью стандартного шаблона основных данных ...)

Во время процесса импорта я сохраняю 2 разных способа. Каждые 1000 элементов, которые я сохраняю в фоновом контексте:

BOOL saveSuccess = [backgroundContext save:&error];

Затем в конце процесса импорта я сохраняю в контексте master / parent, который, якобы, подталкивает изменения к другим дочерним контекстам, включая основной контекст:

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





Related