我有一个Core Data应用程序,我打算用新架构进行更新。轻量级迁移似乎有效,但需要时间与数据库中的数据量成比例。这发生在应用的didFinishLaunchingWithOptions
阶段。
我想避免<app> failed to launch in time
个问题,所以我假设我无法在didFinishLaunchingWithOptions
方法中保留迁移。
我认为最好的方法是在后台线程中执行迁移。我还假设我需要推迟加载主ViewController,直到加载完成,以避免使用managedObjectContext
直到初始化完成。
这是否有意义,是否有这种初始化的示例代码(可能在Apple示例项目中)?
答案 0 :(得分:13)
您无法将迁移放在NSOperation
中,因为它需要在主线程上运行。您需要做的是在不触及Core Data堆栈的情况下退出-applicationDidFinishLaunching:
方法。如果您可以快速完成该方法(并且运行循环周期),那么您的应用程序将不会被终止,您可以在用户完成迁移的过程中使用该应用程序。
请在此处查看我的回答:How to switch from Core Data automatic lightweight migration to manual?
澄清我对此的立场。它本来就可能做任何事情。但是,在后台线程上进行迁移是一个坏主意。很难保证在迁移过程中堆栈永远不会被触及以及其他一些特定于线程的复杂问题。
可能这样做但它涉及的风险很高,完全没必要。可以并且应该使用主线程来执行主Core Data堆栈的迁移。很容易建立一个模态对话框让用户知道正在进行迁移,然后在主线程上执行迁移。
如果您的迁移过程需要花费大量时间,那么强烈建议您使用映射模型从自动迁移切换到手动迁移,以便您可以:
自从最初回答以来,已经发生了很多变化。
现在,迁移的答案是在后台队列中触发它们(通过dispatch_async
NSPersistentStoreCoordinator
来电的addStore...
。
这也意味着您需要确保您的UI可以处理持久层是空的/在未知的时间段内不可用。如何做到这一点取决于你的申请。
例如,您可以拥有一个临时UI,在持久层执行迁移时显示跳舞的猫。用户体验取决于您。
但是, NOT 希望让用户在迁移过程中创建数据。这将使以后很难合并(如果有的话)。
答案 1 :(得分:7)
对不起马库斯,我不得不恭敬地不同意。您可以在后台迁移。
我的迁移在后台线程上运行。在慢速设备上可能需要10秒以上,因此我在后台线程上启动它并使用特定的模态视图控制器来显示进度。
这样做的方法是将正常的加载顺序分为两个阶段。
阶段1) 执行通常在启动时执行的所有操作,不需要托管对象。此阶段的结束由检查确定,以确定是否需要迁移。
阶段2) 执行通常在启动时发生的所有需要托管对象的事情。当不需要迁移时,此阶段是阶段1)的直接延续。
这样,无论迁移处理的持续时间如何,您的应用都会完成启动。
要成功执行长时间迁移,我使用模态视图控制器向用户显示迁移进度的反馈。然后我在后台线程中开始迁移,而模态视图控制器使用KVO来更新它的进度条。
在迁移结束时,它关闭整个核心数据“堆栈”,对主线程的回调将关闭模态并继续到阶段2)。
这整个过程完美无缺,尽管我仍然有一个open question方法可以让自动轻量级迁移显示出像手动迁移那样的进展。
答案 2 :(得分:2)
这是类似于ohhorob描述的方法的概述。不同之处在于我只是显示动画“升级......”HUD,而不是更具信息性的进度条。
关键是,在迁移有机会完成任务之前,没有任何东西会尝试访问Core Data。因此,我将可能触及Core Data的所有剩余启动代码(包括rootViewController的设置)移动到单独的方法中。然后,我在迁移完成后调用此方法,如果不需要迁移,则立即调用此方法。看起来像这样:
-(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
}
这种方法应该与监视启动时间的监视程序一起使用,因为迁移将在主线程之外进行。
答案 3 :(得分:0)
您可以将核心数据更新放入NSOperation
,可以通过覆盖操作的didFinishLaunching...
方法将其添加到-main
中的操作队列中,并且可以在后台运行。
看看this tutorial page,了解所涉及的内容。将KVO与操作的isFinished
属性一起使用以更新应用程序的状态 - 您可以使用此键的值警告用户迁移仍在进行,例如,在显示任何数据之前。
答案 4 :(得分:0)
我刚刚发现了一种非常简单的方法。只需将所有调用包装在应用程序中:didFinishLaunching with dispatch_async到主线程。这将立即返回,让您在主线程上执行升级。但是,您可能应该设置窗口,以便在迁移时不显示空白屏幕。
- (void)application:didFinishLaunching {
dispatch_async(main_queue) {
// migration
}
self.window = ...
return YES;
}