如何最好地处理CoreData + iOS状态恢复?

bpa*_*apa 24 core-data ios ios6 state-restoration

我正在尝试将iOS 6 State Restoration添加到我刚刚完成的应用程序中.这是一个应用程序,其中的模型主要来自CoreData.

按照建议,我使用"传递接力棒"方法在视图控制器之间移动托管对象上下文 - 我在我的App Delegate中创建MOC,将其传递给第一个View Controller,后者将其传递给prepareForSegue:中的第二个,它将它传递给prepareForSegue:中的第三个,等等.

这似乎与国家恢复没有太大关系.我唯一能想到的就是直接在我的App Delegate中从viewControllerWithRestorationIdentifierPath:coder:的实现中检索MOC.实际上,看起来Apple开发人员在观看WWDC会话时做了类似的事情.

这是最好/唯一的方式吗?状态恢复是否有效地打破了Pass-The-Baton,至少对于恢复的视图控制器?

que*_*ish 9

为了熟悉状态恢复,我强烈推荐WWDC 2013会议状态恢复的新功能.虽然一年前在iOS 6中引入了状态恢复,但iOS 7带来了一些显着的变化.

向前传递

使用"传递接力棒"方法,在某个时刻NSManagedObjectContext创建一个根并NSPersistentStoreCoordinator附加一个根.上下文传递给视图控制器,后续子视图控制器又传递根上下文或子上下文.

例如,当用户启动应用程序时,将NSManagedObjectContext创建根并将其传递到管理其的根视图控制器NSFetchedResultsController.当用户选择视图控制器中的项目时,将创建新的详细视图控制器并NSManagedObjectContext传入实例.

传递托管对象上下文指挥棒

拯救和恢复国家

状态恢复以对视图控制器中使用Core Data的应用程序重要的方式对其进行更改.如果用户在详细视图控制器上并将应用程序发送到后台,则系统会创建一个恢复存档,其中包含用于重建离开时可见状态的信息.写出关于整个视图控制器链的信息,并且当重新启动应用程序时,这用于重建状态.

国家恢复

当发生这种情况时,它不使用任何自定义初始化器,segue等.该UIStateRestoring协议定义用于编码和解码状态的方法,允许某种程度的定制.符合的对象NSCoding可以存储在恢复存档中,并且在iOS 7中,状态恢复已扩展到模型对象和数据源.

状态恢复旨在仅存储重建应用程序的可见状态所需的信息.对于Core Data应用程序,这意味着存储在正确的持久存储中定位对象所需的信息.

从表面上看,这似乎很简单.在视图控制器管理的情况下,NSFetchedResultsController这可能意味着存储谓词和排序描述符.对于显示或编辑单个托管对象的详细视图控制器,托管对象的URI表示将添加到状态恢复归档中:

- (void) encodeRestorableStateWithCoder:(NSCoder *)coder {
    NSManagedObjectID   *objectID   = [[self managedObject] objectID];

    [coder encodeObject:[objectID URIRepresentation] forKey:kManagedObjectKeyPath];
    [super encodeRestorableStateWithCoder:coder];
}
Run Code Online (Sandbox Code Playgroud)

恢复状态后,将调用UIStateRestoring方法-decodeRestorableStateWithCoder:从归档信息中恢复对象:

  1. 解码来自还原存档的URI.
  2. 从持久性存储协调器获取URI的托管对象ID
  3. 从该托管对象ID的托管对象上下文中获取托管对象实例

例如:

- (void) decodeRestorableStateWithCoder:(NSCoder *)coder {
    NSURL               *objectURI  = nil;
    NSManagedObjectID   *objectID   = nil;
    NSPersistentStoreCoordinator    *coordinator    = [[self managedObjectContext] persistentStoreCoordinator];

    objectURI = [coder decodeObjectForKey:kManagedObjectKeyPath];
    objectID = [coordinator managedObjectIDForURIRepresentation:objectURI];
    [[self managedObjectContext] performBlock:^{
        NSManagedObject *object = [self managedObjectContext] objectWithID:objectID];
        [NSOperationQueue mainQueue] addOperationWithBlock:^{
            [self setManagedObject:object];
        }];
    }]; 
}
Run Code Online (Sandbox Code Playgroud)

这就是事情变得更加复杂的地方.在应用程序生命周期中-decodeRestorableStateWithCoder:调用视图控制器的点将需要正确的 NSManagedObjectContext.

通过接力棒与状态恢复:战斗!

通过"传递接力棒"方法,视图控制器被实例化为用户交互的结果,并且传入了托管对象上下文.该管理对象上下文连接到父上下文或持久性存储协调器.

在状态恢复期间,没有发生.如果你看看"传递接力棒"与状态恢复期间发生的事情的插图,它们可能看起来非常相似 - 而且它们是.在状态恢复数据在-一起传递NSCoder表示要恢复归档接口实例.

遗憾的是,NSManagedObjectContext我们要求的信息无法存储为恢复存档的一部分.NSManagedObjectContext 确实符合NSCoding,但重要部分没有.NSPersistentStoreCoordinator没有,所以不会坚持下去.奇怪的是,一个人的parentContext财产NSManagedObjectContext也不会(我强烈建议在此提交雷达).存储特定NSPersistentStore实例的URL 并NSPersistentStoreCoordinator在每个视图控制器中重新创建可能看起来是一个有吸引力的选项,但结果将是每个视图控制器的不同协调器 - 这可能很快导致灾难.

因此,虽然状态恢复可以提供定位实体所需的信息NSManagedObjectContext,但它不能直接提供重建上下文本身所需的内容.

接下来呢?

最终,视图控制器所需要的-decodeRestorableStateWithCoder:NSManagedObjectContext具有与编码状态时相同的父实例.它应该具有与祖先上下文和持久存储相同的结构.

状态恢复开始在UIApplicationDelegate,几个委托方法被调用作为恢复处理(的一部分-application:willFinishLaunchingWithOptions:,-application:shouldRestoreApplicationState:,-didDecodeRestorableStateWithCoder:,-application:viewControllerWithRestorationIdentifierPath:coder:).这些中的每一个都是从一开始就定制恢复过程并传递信息的机会 - 例如将NSManagedObjectContext实例附加为NSCoder用于恢复的关联对象引用.

如果应用程序委托对象负责创建根上下文,则一旦启动过程完成(有或没有状态恢复),该对象可以在整个视图控制器链中向下推送.每个视图控制器都会将相应的NSManagedObjectContext实例传递给它的子视图控制器:

@implementation UIViewController (CoreData)

- (void) setManagedObjectContext:(NSManagedObjectContext *)context {
    [[self childViewControllers] makeObjectsPerformSelector:_cmd withObject:context];
}

@end
Run Code Online (Sandbox Code Playgroud)

并且每个提供它自己的实现的视图控制器都会创建它自己的子上下文.这还有其他优点 - 任何使托管对象上下文的用户对其做出反应的方法都会使得更容易异步创建上下文.创建上下文本身既快又轻,但是将持久存储添加到根上下文可能非常昂贵,不应该允许在主队列上运行.许多应用程序在应用程序委托方法的主队列上执行此操作,并且在打开存储文件花费太长时间或需要迁移时最终被操作系统杀死.在另一个线程上添加持久性存储,然后将上下文发送到准备就绪时使用它的对象可以帮助防止这些类型的问题.

另一种方法可能是在视图控制器中利用响应者链.在状态恢复期间,视图控制器可以走动响应链以查找链上的下一个NSManagedObjectContext链,创建子上下文并使用它.使用非正式协议实现这一点非常简单,并且可以提供灵活且适应性强的解决方案.

非正式协议的默认实现将在响应者链中走得更远:

@implementation UIResponder (CoreData)

- (NSManagedObjectContext *) managedObjectContext {
    NSManagedObjectContext    *result = nil;

    if ([self nextResponder] != nil){
        if ([[self nextResponder] respondsToSelector:@selector(managedObjectContext)]){
            result = [[self nextResponder] managedObjectContext];
        }
    }
    return result;
}

@end
Run Code Online (Sandbox Code Playgroud)

响应者链中的任何对象都可以实现-managedObjectContext以提供替代实现.这包括应用程序委托,它确实参与响应程序链.使用上面的非正式协议,如果视图或视图控制器调用-managedObjectContext默认实现,将一直到应用程序委托返回结果,除非沿途的其他一些对象提供了非零结果.

您还可以选择使用具有状态恢复的恢复类工厂来在恢复期间重建托管对象上下文链.

这些解决方案并不适合所有应用或情况,只有您可以决定什么对您有用.


Shi*_*ami -2

我从NSScreencast学到了一种非常干净的设置核心数据堆栈的方法。基本上,您无需选择“使用核心数据”选项即可启动 Xcode 项目。然后添加一个单例类,它是您的数据模型。因此,要获得主要的 MOC,您需要这样做[[DataModel sharedModel] mainContext]。我发现将所有内容都转储到应用程序代理中要干净得多。

我从来没有这样使用过它,但我想在你的情况下你也可以在你的视图控制器中这样做:

-(NSManagedObjectContext*)moc
{ 
    if (_moc != nil) return _moc;
    _moc = [[DataModel sharedModel] mainContext];
    return _moc;
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,但这与国家恢复无关。问题不在于如何设置托管对象上下文,而在于如何使其与状态恢复良好配合。 (2认同)
  • 仅供参考,这是用一个单例交换另一个单例。如果您要使用单例,不妨将其保留在应用程序委托中。 (2认同)