简体   繁体   English

iPhone应用程序启动时间和Core Data迁移

[英]iPhone app launch times and Core Data migration

I have a Core Data application which I plan to update with a new schema. 我有一个Core Data应用程序,我打算用新架构更新。 The lightweight migration seems to work, but it takes time proportional to the amount of data in the database. 轻量级迁移似乎有效,但需要时间与数据库中的数据量成比例。 This occurs in the didFinishLaunchingWithOptions phase of the app. 这发生在应用程序的didFinishLaunchingWithOptions阶段。

I want to avoid <app> failed to launch in time problems, so I assume I cannot keep the migration in the didFinishLaunchingWithOptions method. 我想避免<app> failed to launch in time问题,因此我假设我无法在didFinishLaunchingWithOptions方法中保留迁移。

I assume the best method would involve performing the migration in a background thread. 我假设最好的方法是在后台线程中执行迁移。 I assume also that I'd need to defer loading of the main ViewController until the loading completes to avoid using the managedObjectContext until initialization completes. 我还假设我需要推迟加载主ViewController,直到加载完成,以避免在初始化完成之前使用managedObjectContext

Does this make sense, and is there example code (maybe in Apple sample projects) of this sort of initialization? 这是否有意义,是否有这种初始化的示例代码(可能在Apple示例项目中)?

You can't put migration in a NSOperation because it will need to run on the main thread. 您不能将迁移放在NSOperation因为它需要在主线程上运行。 What you need to do is to get out of the -applicationDidFinishLaunching: method without touching the Core Data stack. 您需要做的是退出-applicationDidFinishLaunching:方法而不触及Core Data堆栈。 If you can finish that method (and that run loop cycle) quickly then your app will not be terminated and you can take as long as the user will put up with to finish your migration. 如果您可以快速完成该方法(并且运行循环周期),那么您的应用程序将不会被终止,只要用户愿意完成您的迁移,您就可以使用该应用程序。

See my answer here: How to switch from Core Data automatic lightweight migration to manual? 请参阅我的答案: 如何从Core Data自动轻量级迁移切换到手动?

Update May 19, 2010 2010年5月19日更新

To clarify my position on this. 澄清我对此的立场。 It is inherently possibly to do just about anything. 它本来就可能做任何事情。 However, doing a migration on a background thread is a bad idea . 但是,在后台线程上进行迁移是个坏主意 It is extremely difficult to guarantee that the stack will never get touched during the migration as well as a whole host of other threading specific complications. 很难保证在迁移过程中堆栈永远不会被触及以及其他一些特定于线程的复杂问题。

It is possible to do it but it involves a high degree of risk that is completely unnecessary. 可以做到,但它涉及高风险,这是完全没有必要的。 The main thread can and should be used to do a migration of the main Core Data stack. 可以并且应该使用主线程来执行主Core Data堆栈的迁移。 It is easy to put up a modal dialog to let the user know a migration is occurring and then perform the migration on the main thread. 很容易建立一个模态对话框让用户知道正在进行迁移,然后在主线程上执行迁移。

If you are in a situation where your migrations are taking a signficant amount of time then it is also highly recommended that you switch from automatic migration to manual migration with a mapping model so that you can: 如果您的迁移过程需要花费大量时间,那么强烈建议您使用映射模型从自动迁移切换到手动迁移,以便您可以:

  • Easily back out of the migration if needed. 如果需要,可以轻松退出迁移。
  • Perform the migration in chunks in case the user quits your application. 如果用户退出您的应用程序,则以块的形式执行迁移。
  • Give the user solid feedback on how far along the migration is and when it will be done. 为用户提供有关迁移距离和完成时间的可靠反馈。

Update December 15, 2015 2015年12月15日更新

A lot has changed since this was originally answered. 自从最初回答以来,已经发生了很多变化。

The answer now for migrations is to fire them on a background queue (via dispatch_async of the NSPersistentStoreCoordinator 's addStore... call). 现在迁移答案是解雇他们在后台队列(通过dispatch_async中的NSPersistentStoreCoordinatoraddStore...调用)。

This also means that you need to make sure your UI can handle the persistence layer being empty/not available for an unknown period of time. 这也意味着您需要确保您的UI可以处理持久层是空的/在未知的时间段内不可用。 How to do that depends on your application. 如何做到这一点取决于您的应用程序。

For example, you can have a temporary UI that shows dancing cats while the persistence layer does the migration. 例如,您可以拥有一个临时UI,在持久层执行迁移时显示跳舞的猫。 The user experience is up to you. 用户体验取决于您。

However, you do NOT want to let the user create data while the migration is happening. 但是,您希望让用户在迁移过程中创建数据。 That will make it difficult to merge later (if at all). 这将使以后很难合并(如果有的话)。

Sorry Marcus, I have to respectfully disagree. 对不起马库斯,我不得不恭敬地不以为然。 You can migrate in the background. 可以在后台迁移。

My migriation runs on a background thread. 我的迁移在后台线程上运行。 It can take over 10 seconds on slow devices, so I start it on a background thread and have a specific modal view controller to show progress. 在慢速设备上可能需要10秒以上,因此我在后台线程上启动它并使用特定的模态视图控制器来显示进度。

The way to do this is split your normal loading sequence into two phases. 这样做的方法是将正常的加载顺序分为两个阶段。

Phase 1) Do everything you would normally do at launch that doesn't require managed objects. 阶段1)执行在启动时通常不需要托管对象的所有操作。 The end of this phase is defined by a check to determine if migration is required. 此阶段的结束由检查确定,以确定是否需要迁移。

Phase 2) Do everything that normally happens at launch that requires managed objects. 阶段2)执行在启动时通常发生的所有需要​​托管对象的事情。 This phase is an immediate continuation of Phase 1) when no migration was required. 当不需要迁移时,此阶段是阶段1)的立即延续。

This way, your app finishes launching regardless of the duration of migration processing. 这样,无论迁移处理的持续时间如何,您的应用都会完成启动。

To perform a long migration successfully, I use a modal view controller showing feedback of the migration progress to the user. 为了成功执行长迁移,我使用模态视图控制器向用户显示迁移进度的反馈。 I then commence the migration in the background thread, while the modal view controller uses KVO to update it's progress bar. 然后我在后台线程中开始迁移,而模态视图控制器使用KVO更新它的进度条。

At the end of the migration, it closes down the whole core data "stack", and a callback to the main thread will dismiss the modal and continue on to Phase 2). 在迁移结束时,它关闭整个核心数据“堆栈”,并且对主线程的回调将关闭模态并继续到阶段2)。

This whole process works flawlessly, although I still have an open question on a way to have the automatic lightweight migration reveal it's progress like the manual migration does. 这整个过程完美无瑕,尽管我仍然有一个开放的问题 ,即自动轻量级迁移的方式揭示了它的进展,就像手动迁移一样。

Here is an outline of an approach similar to what ohhorob describes. 这是类似于ohhorob所描述的方法的概述。 The difference is that I just display an animated "Upgrading..." HUD rather than a more informative progress bar. 不同之处在于我只是显示动画“升级......”HUD而不是更具信息性的进度条。

The key point is that nothing tries to access Core Data until the migration has had a chance to do its thing. 关键的一点是,在迁移有机会完成其任务之前,没有任何东西会尝试访问Core Data。 Therefore, I move all the remaining launch code (including the setting of the rootViewController) that could potentially touch Core Data into a separate method. 因此,我将可能触及Core Data的所有剩余启动代码(包括rootViewController的设置)移动到单独的方法中。 I then call this method after the migration has completed, or immediately if no migration is required. 然后,我在迁移完成后调用此方法,如果不需要迁移,则立即调用此方法。 That looks like this: 看起来像这样:

-(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
}

This approach should play nice with the watchdog that monitors startup times since the migration will take place off the main thread. 这种方法应该与监视启动时间的监视程序一起使用,因为迁移将在主线程之外进行。

I just found a pretty simple approach. 我刚刚发现了一个非常简单的方法。 Just wrap all your calls in application:didFinishLaunching with a dispatch_async to the main thread. 只需将所有调用包装在应用程序中:didFinishLaunching with dispatch_async到主线程。 This will return right away and let you perform your upgrade on the main thread. 这将立即返回,让您在主线程上执行升级。 However, you should probably set your window so that it doesn't display a blank screen while migrating. 但是,您应该设置窗口,以便在迁移时不显示空白屏幕。

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

You might put your Core Data updates into an NSOperation , which can be added to an operations queue in didFinishLaunching... and which can operate in the background, by overriding the operation's -main method. 您可以将核心数据更新放入NSOperation ,可以通过覆盖操作的-main方法将其添加到didFinishLaunching...的操作队列中,并且可以在后台运行。

Take a look at this tutorial page to get a general idea of what's involved. 请查看本教程页面 ,了解所涉及的内容。 Use KVO with the operation's isFinished property to update the state of your application -- you might use this key's value to warn the user that the migration is still taking place, for example, before presenting any data. KVO与操作的isFinished属性一起使用以更新应用程序的状态 - 您可以使用此键的值来警告用户迁移仍在进行,例如,在显示任何数据之前。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM