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

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

Here's the setup:

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

<code>[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
</code>

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

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

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

Use Autorelease Pools to keep the memory down Relationships Cost. Import flat, then patch up relationships at the end Don't query if you can help it, it slows things down in an O(n^2) manner Import in Batches: save, reset, drain and repeat Turn off the Undo Manager on import

iDeveloper TV - Core Data Performance

Use 3 Contexts: Master, Main and Confinement context types

iDeveloper TV - Базовые данные для Mac, iPhone & amp; Обновление iPad

Running saves on other queues with performBlock makes things fast. Encryption slows things down, turn it off if you can.

Импорт и отображение больших наборов данных в базовых данных. Автор Marcus Zarra

You can slow down the import by giving time to the current run loop, so things feel smooth to the user. Sample Code proves that it is possible to do large imports and keep the UI responsive, but not as fast as with 3 contexts and async saving to disk. My Current Solution

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

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

<code>masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
</code>

mainManagedObjectContext - Это контекст, который пользовательский интерфейс использует везде. Это дочерний элемент masterManagedObjectContext. Я создаю это так:

<code>mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
</code>

backgroundContext - Этот контекст создан в моем подклассе NSOperation, который отвечает за импорт данных XML в Core Data. Я создаю его в основном методе операции и связываю его с основным контекстом.

<code>backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
</code>

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

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

<code>BOOL saveSuccess = [backgroundContext save:&error];
</code>

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

<code>[masterManagedObjectContext performBlock:^{
   NSError *parentContextError = nil;
   BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
</code>

Problem: Проблема в том, что мой пользовательский интерфейс не будет обновляться, пока я не перезагрузлю представление.

У меня есть простой UIViewController с UITableView, который подается данные с помощью NSFetchedResultsController. Когда процесс импорта завершается, NSFetchedResultsController не видит изменений из родительского / основного контекста, и поэтому пользовательский интерфейс не обновляется автоматически, как я привык видеть. Если я вытолкну UIViewController из стека и загрузлю его снова, все данные будут там.

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

Я пробовал следующее, которое просто зависает приложение:

<code>- (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];
}
</code>

Ответы на вопрос(1)

Ваш ответ на вопрос