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


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

вот настройки:

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

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

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

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

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

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

iDeveloper TV - Core Data Performance

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

iDeveloper TV-Core Data for Mac, iPhone & iPad Update

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

импорт и отображение больших наборов данных в Core Data by Marcus Zarra

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

backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];

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

во время процесса импорта я сохраняю 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];
}
1 97

1 ответ:

вы, вероятно, должны сохранить мастер MOC в шагах, а также. Нет смысла, что MOC ждать до конца, чтобы спасти. Он имеет свой собственный поток, и это поможет сохранить память вниз, а также.

Вы писали:

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

в вашей конфигурации, у вас двое детей (основной MOC и фоновый MOC), оба родились в "мастер."

когда вы экономите на ребенке, он толкает изменения в родительскую. Другие дочерние элементы этого MOC будут видеть данные при следующем выполнении выборки... они явно не уведомил.

Итак, когда BG сохраняет, его данные передаются мастеру. Обратите внимание, однако, что ни одна из этих данных не находится на диске, пока мастер не сохранит. Кроме того, любые новые элементы не будут получать постоянные идентификаторы, пока мастер не сохранит диск.

в вашем сценарии вы вытягиваете данные в основной MOC, сливаясь с главным сохранением во время уведомления DidSave.

Это должно работать, поэтому мне любопытно, где он "висел."Я отмечу, что вы не работаете на основном потоке MOC каноническим образом (по крайней мере, не для iOS 5).

кроме того, вы, вероятно, заинтересованы только в слиянии изменений с master MOC (хотя ваша регистрация выглядит так, как будто это только для этого). Если Я должен был использовать update-on-did-save-notification, я бы это сделал...

- (void)contextChanged:(NSNotification*)notification {
    // Only interested in merging from master into main.
    if ([notification object] != masterManagedObjectContext) return;

    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];

        // NOTE: our MOC should not be updated, but we need to reload the data as well
    }];
}

теперь, для того, что может быть настоящим вопроса висят... вы показываете два разных вызова, чтобы сэкономить на мастере. первый хорошо защищен в своем собственном performBlock, но второй нет (хотя вы можете вызывать saveMasterContext в performBlock...

однако, я бы также изменил этот код...

- (void)saveMasterContext {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];    
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];

    // Make sure the master runs in it's own thread...
    [masterManagedObjectContext performBlock:^{
        NSError *error = nil;
        BOOL saveSuccess = [masterManagedObjectContext save:&error];
        // Handle error...
        [notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
    }];
}

однако, обратите внимание, что главный-это дитя мастера. Так что, это не должно быть нужно объединить изменения. Вместо этого просто понаблюдайте за DidSave на Мастер, и просто повторно получить! Данные уже находятся в вашем Родителе, просто ожидая, когда вы попросите об этом. Это одно из преимуществ наличия данных в родителе в первую очередь.

другой вариант (и мне было бы интересно услышать о ваших результатах-это много данных)...

вместо того, чтобы сделать фоновый MOC дочерним элементом мастера, сделайте его дочерним элементом ГЛАВНЫЙ.

сделать это. Каждый раз, когда BG сохраняет, он автоматически получает толкнул в основной. Теперь главный должен вызвать save, а затем мастер должен вызвать save, но все это делает перемещение указателей... пока мастер не сохранит на диск.

красота этого метода заключается в том, что данные идут из фонового MOC прямо в ваши приложения MOC (затем проходит, чтобы получить сохраненные).

здесь некоторые штраф за проход, но весь тяжелый подъем выполняется в Мастере, когда он попадает на диск. И если вы пинаете эти сохранения на мастере с performBlock, то main thread просто отправляет запрос и немедленно возвращается.

пожалуйста, дайте мне знать, как она идет!