Время запуска приложений iPhone и перенос основных данных

У меня есть приложение Core Data, которое я планирую обновить с помощью новой схемы. Легкая миграция, похоже, работает, но она требует времени, пропорционального количеству данных в базе данных. Это происходит в фазе didFinishLaunchingWithOptions приложения.

Я хочу избежать проблем <app> failed to launch in time, поэтому я предполагаю, что не могу сохранить миграцию в методе didFinishLaunchingWithOptions.

Я предполагаю, что лучший метод предполагает выполнение миграции в фоновом потоке. Я также предполагаю, что мне нужно будет отложить загрузку основного ViewController до завершения загрузки, чтобы избежать использования managedObjectContext до завершения инициализации.

Это имеет смысл, и есть ли пример кода (возможно, в примерах проектов Apple) такого типа инициализации?

Ответ 1

Вы не можете переносить миграцию в NSOperation, потому что ее нужно будет запускать в основном потоке. Что вам нужно сделать, так это выйти из метода -applicationDidFinishLaunching:, не касаясь стека Core Data. Если вы можете быстро завершить этот метод (и этот цикл цикла цикла), ваше приложение не будет завершено, и вы можете занять столько же времени, сколько пользователь выполнит, чтобы завершить миграцию.

См. мой ответ здесь: Как переключиться с автоматической переносимости данных Core Data на ручной?

Обновление от 19 мая 2010 г.

Прояснить свою позицию по этому вопросу. По своей сути возможно что-то делать. Однако выполнение миграции в фоновом потоке - это плохая идея. Чрезвычайно сложно гарантировать, что во время миграции стек будет никогда затронут, а также целый ряд других специфических проблем с потоком.

Это можно сделать, но это связано с высокой степенью риска, который совершенно не нужен. Основной поток можно и нужно использовать для миграции основного пакета Core Data. Легко разместить модальный диалог, чтобы пользователь знал, что происходит миграция, а затем выполнить миграцию в основном потоке.

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

  • Легко вернуться из миграции, если это необходимо.
  • Выполните миграцию в кусках, если пользователь покидает ваше приложение.
  • Дайте пользователю надежную обратную связь о том, как далеко продвигается миграция, и когда это будет сделано.

Обновление 15 декабря 2015 г.

Много изменилось с тех пор, как это было изначально получено.

Теперь ответ на миграцию - это запустить их в фоновом режиме (через dispatch_async вызова NSPersistentStoreCoordinator addStore...).

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

Например, вы можете иметь временный интерфейс, который показывает танцевальных кошек, в то время как уровень персистентности выполняет миграцию. Пользовательский опыт зависит от вас.

Однако вы НЕ хотите, чтобы пользователь мог создавать данные во время миграции. Это затруднит слияние позже (если вообще).

Ответ 2

Жаль Маркуса, я должен с уважением не соглашаться. Вы можете выполнить миграцию в фоновом режиме.

Моя миграция работает на фоновом потоке. Это может занять более 10 секунд на медленных устройствах, поэтому я запускаю его в фоновом потоке и имею специальный диспетчер режимов для отображения прогресса.

Способ сделать это - раскол вашей обычной последовательности загрузки на две фазы.

Фаза 1) Сделайте все, что вы обычно делаете при запуске, не требующем управляемых объектов. Конец этой фазы определяется проверкой, чтобы определить, требуется ли миграция.

Фаза 2) Выполняйте все, что обычно происходит при запуске, для которого требуются управляемые объекты. Эта фаза является непосредственным продолжением фазы 1), когда миграция не требуется.

Таким образом, ваше приложение завершает запуск независимо от продолжительности обработки миграции.

Чтобы успешно выполнить длинный перенос, я использую контроллер модального представления, отображающий обратную связь хода перехода к пользователю. Затем я начинаю миграцию в фоновом потоке, в то время как контроллер модального представления использует KVO для обновления строки выполнения.

В конце миграции он закрывает весь "стек" данных ядра, а обратный вызов основного потока отклоняет модальный режим и переходит к этапу 2).

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

Ответ 3

Вот схема подхода, аналогичного тому, что описывает охороб. Разница в том, что я просто показываю анимированный "Модернизирующий..." HUD, а не более информативный индикатор выполнения.

Ключевым моментом является то, что ничто не пытается получить доступ к Core Data до тех пор, пока миграция не сможет выполнить свою задачу. Поэтому я перемещаю весь оставшийся код запуска (включая настройку rootViewController), который потенциально может коснуться Core Data в отдельный метод. Затем я вызываю этот метод после завершения миграции или немедленно, если миграция не требуется. Это выглядит так:

-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    [self.window makeKeyAndVisible];

    if ([MyManagedObject doesRequireMigration]) { // -doesRequireMigration is a method in my RHManagedObject Core Data framework
        [SVProgressHUD showWithStatus:NSLocalizedString(@"Upgrading...", nil) maskType:SVProgressHUDMaskTypeGradient];

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

            // do your migration here

            dispatch_async(dispatch_get_main_queue(), ^{
                [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"Success!",nil)];
                [self postApplication:application didFinishLaunchingWithOptions:launchOptions];
            });
        });

    } else {
        [self postApplication:application didFinishLaunchingWithOptions:launchOptions];
    }

    return YES;
}

-(void)postApplication:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window.rootViewController = ....

    // anything else you want to run at launch
}

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

Ответ 4

Вы можете поместить обновления Core Data в NSOperation, которые могут быть добавлены в очередь операций в didFinishLaunching... и которые может работать в фоновом режиме, переопределяя метод операции -main.

Взгляните на эту страницу руководства, чтобы получить общее представление о том, что задействовано. Используйте KVO с свойством isFinished для обновления состояния вашего приложения - вы можете использовать это значение ключа, чтобы предупредить пользователя о том, что миграция по-прежнему происходит, например, перед представлением любых данных.

Ответ 5

Я просто нашел довольно простой подход. Просто заверните все свои вызовы в приложении: didFinishLaunching с dispatch_async в основной поток. Это немедленно вернется и позволит вам выполнить обновление по основному потоку. Тем не менее, вы должны, вероятно, установить свое окно, чтобы оно не отображало пустой экран во время переноса.

- (void)application:didFinishLaunching {
     dispatch_async(main_queue) {
          // migration
     }
     self.window = ...
     return YES;
}