【问题标题】:iPhone app launch times and Core Data migrationiPhone 应用程序启动时间和核心数据迁移
【发布时间】:2011-02-21 01:33:09
【问题描述】:

我有一个 Core Data 应用程序,我计划使用新架构进行更新。轻量级迁移似乎可行,但它所花费的时间与数据库中的数据量成正比。这发生在应用程序的didFinishLaunchingWithOptions 阶段。

我想避免<app> failed to launch in time 问题,所以我假设我不能在didFinishLaunchingWithOptions 方法中保持迁移。

我认为最好的方法是在后台线程中执行迁移。我还假设我需要将主 ViewController 的加载推迟到加载完成,以避免在初始化完成之前使用 managedObjectContext

这有意义吗,是否有这种初始化的示例代码(可能在 Apple 示例项目中)?

【问题讨论】:

    标签: iphone cocoa core-data


    【解决方案1】:

    您不能将迁移放在NSOperation 中,因为它需要在主线程上运行。您需要做的是在不触及核心数据堆栈的情况下退出-applicationDidFinishLaunching: 方法。如果您可以快速完成该方法(以及该运行循环周期),那么您的应用将不会被终止,并且您可以在用户能够忍受的时间内完成迁移。

    在这里查看我的答案:How to switch from Core Data automatic lightweight migration to manual?

    2010 年 5 月 19 日更新

    澄清我对此的立场。它本质上可能做任何事情。但是,在后台线程上进行迁移是一个坏主意。很难保证堆栈在迁移过程中永远不会受到影响,以及其他大量线程特定的复杂情况。

    这样做是可能,但它涉及完全不必要的高度风险。主线程可以而且应该用于迁移主核心数据堆栈。很容易建立一个模态对话框,让用户知道正在发生迁移,然后在主线程上执行迁移。

    如果您的迁移需要大量时间,那么强烈建议您使用映射模型从自动迁移切换到手动迁移,以便您可以:

    • 如果需要,可以轻松退出迁移。
    • 分块执行迁移,以防用户退出您的应用程序。
    • 就迁移的进度以及何时完成向用户提供可靠的反馈。

    2015 年 12 月 15 日更新

    自从最初回答后发生了很多变化。

    现在迁移的答案是在后台队列中触发它们(通过NSPersistentStoreCoordinatoraddStore... 调用的dispatch_async)。

    这也意味着您需要确保您的 UI 可以处理持久层为空/在未知时间段内不可用的情况。如何做到这一点取决于您的应用程序。

    例如,您可以有一个显示跳舞猫的临时 UI,而持久层执行迁移。用户体验取决于您。

    但是,您确实希望让用户在迁移过程中创建数据。这将使以后难以合并(如果有的话)。

    【讨论】:

    • 我想知道这是否仍然是您对当前最佳实践(大约 2015 年)的看法?谢谢!
    • 不,我会将迁移放在后台队列中,让它在 UI 完成之前完成。我最近关于核心数据堆栈的讨论反映了这种变化。
    【解决方案2】:

    对不起,马库斯,我不得不恭敬地不同意。您可以在后台迁移。

    我的迁移在后台线程上运行。在慢速设备上可能需要 10 多秒,所以我在后台线程上启动它并使用特定的模式视图控制器来显示进度。

    这样做的方法是将您的正常加载顺序分为两个阶段。

    第一阶段) 做你在启动时通常会做的所有不需要托管对象的事情。此阶段的结束由检查以确定是否需要迁移来定义。

    第 2 阶段) 执行通常在启动时发生的所有需要​​托管对象的事情。此阶段是不需要迁移的阶段 1) 的直接延续。

    这样,无论迁移处理的持续时间如何,您的应用都会完成启动。

    为了成功执行长时间迁移,我使用了一个模态视图控制器,向用户显示迁移进度的反馈。然后我在后台线程中开始迁移,而模态视图控制器使用 KVO 更新它的进度条。

    在迁移结束时,它会关闭整个核心数据“堆栈”,并且对主线程的回调将关闭模式并继续到阶段 2)。

    整个过程完美无缺,尽管我仍然有一个open question 可以让自动轻量级迁移显示它的进度,就像手动迁移一样。

    【讨论】:

    • 这听起来像是我需要的,除了我必须将NSMainNibFile 的加载推迟到UIApplication,因为我所有的视图都涉及核心数据。我确信有办法手动执行此操作,但我没有找到正确的参考。
    • 当然。我会从 info plist 中删除 NSMainNibFile,并在您知道所有内容都已迁移并准备就绪时加载它。我记得阅读了 James Thomson(PCalc 的开发人员)的 dragthing.com/blog/?p=246,手动执行此操作以帮助他感知启动时间。
    • 感谢您的链接 .. 这就是我想知道的只是害怕获得正确的方法;)如果需要迁移,我 loadNibNamed 加载屏幕并将其添加到窗口,然后 @987654327 @。然后我 performSelector 延迟 0.0 到加载过程的第二部分,它调整了运行循环。然后我加载主视图 .nib ,它会自动将其添加到窗口中(我不清楚它是如何工作的)。
    • 我会把它放在专家级设计中。试图在后台线程上这样做是非常危险的,而且很容易搞砸。对于那些将其作为解决方案阅读的人,我不推荐它ATALL。最好放置完全相同的模态视图,然后在主线程上处理迁移。搞砸用户数据的机会要少得多。因此,我一揽子建议在主线程上进行所有迁移。
    • 我同意马库斯的观点。使用手动迁移比尝试破解自动迁移以在后台线程上运行要安全得多。我学会了对多线程和核心数据非常谨慎。
    【解决方案3】:

    以下是类似于 ohhorob 所描述的方法的概要。不同之处在于我只显示动画“升级中...”HUD,而不是信息量更大的进度条。

    关键是在迁移有机会完成它之前,没有任何东西会尝试访问 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
    }
    

    这种方法应该与监视启动时间的看门狗配合得很好,因为迁移将在主线程之外进行。

    【讨论】:

      【解决方案4】:

      您可以将核心数据更新放入NSOperation,通过覆盖操作的-main 方法,可以将其添加到didFinishLaunching... 中的操作队列中,并且可以在后台操作。

      查看this tutorial page 以大致了解所涉及的内容。将 KVO 与操作的 isFinished 属性一起使用来更新应用程序的状态 - 您可以使用此键的值来警告用户迁移仍在进行中,例如,在显示任何数据之前。

      【讨论】:

      • stackoverflow.com/questions/2840406/… 可能在某些方面有所帮助。
      • 您不能将迁移放在 NSOperation 中,因为 A) 它需要成为主要上下文,而 NSOperation 会将其放在后台;和 B) 任何在主线程上触及 NSManagedObjectContext 的代码都会在后台线程运行时触发 second 迁移。
      • 我使用NSOperation 实例做后台核心数据工作,所以我认为我必须恭敬地不同意。我再次阅读了迁移指南,其中没有任何内容需要在主线程上完成。
      • 如果您的应用在没有实例化核心数据的情况下无法执行任何操作,那么与阻塞主运行循环相比,使用 NSOperation 有什么优势?
      • 您的 UI 可以在主线程上畅通无阻地运行。例如,您的应用可以向用户提供迁移仍在进行中的 UI 提示。
      【解决方案5】:

      我刚刚找到了一个非常简单的方法。只需将所有调用包装在 application:didFinishLaunching 中,并带有 dispatch_async 到主线程。这将立即返回并让您在主线程上执行升级。但是,您可能应该将窗口设置为在迁移时不会显示空白屏幕。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-12-24
        • 2016-11-11
        • 2014-01-29
        • 2011-01-10
        • 2019-01-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多