如何最好地处理核心数据+ iOS状态恢复?

时间:2013-07-31 22:08:34

标签: ios core-data ios6 state-restoration

我正在尝试将iOS 6状态恢复添加到我刚刚完成的应用中。这是一个应用程序,模型主要来自CoreData。

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

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

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

6 个答案:

答案 0 :(得分:9)

要熟悉状态恢复,我强烈推荐WWDC 2013 session What's New in State Restoration。虽然一年前在iOS 6中引入了状态恢复,但iOS 7带来了一些显着的变化。

向前传递

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

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

Passing the Managed Object Context Baton

保存和恢复状态

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

State Restoration

当发生这种情况时,它不使用任何自定义初始值设定项,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];
}

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

  1. 解码还原存档中的URI。
  2. 从持久性商店协调员
  3. 获取URI的托管对象ID
  4. 从该托管对象ID的托管对象上下文中获取托管对象实例
  5. 例如:

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

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

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

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

    在状态恢复期间不会发生。如果你看看"传递接力棒"与状态恢复相比,他们可能看起来非常相似 - 而且他们是。在状态恢复期间,数据 传递 - 代表恢复存档的接口的NSCoder实例。

    不幸的是,我们要求的NSManagedObjectContext信息无法存储为还原存档的一部分。 NSManagedObjectContext 符合NSCoding,但重要部分不符合NSPersistentStoreCoordinatorparentContext没有,所以它不会被保留。奇怪的是,NSManagedObjectContext的{​​{1}}属性也不会(我强烈建议在此处提交雷达)。 存储特定NSPersistentStore实例的URL并在每个视图控制器中重新创建NSPersistentStoreCoordinator似乎是一个有吸引力的选项,但结果将是每个视图控制器的不同协调器 - 这可能很快导致灾难。 / p>

    因此,虽然状态恢复可以提供在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
    

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

    另一种方法可能是在视图控制器中利用响应者链。在状态恢复期间,视图控制器可以遍历响应者链以查找链上的下一个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
    

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

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

    这些解决方案并不适合所有应用或情况,只有您可以决定哪些适合您。

答案 1 :(得分:3)

我认为处理此问题的最佳方法是将MOC编码为:

- (void)encodeRestorableStateWithCoder:(NSCoder *)coder

然后解码,通过以下方式恢复:

- (void)decodeRestorableStateWithCoder:(NSCoder *)coder

这应保留在状态恢复之间传递接力棒的方法。

请记住,如果您采用这种方法,那么使用MOC的每个VC都应该实现这一点。

要稍微扩展,请使用+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder方法初始化VC,然后应通过上述方法对MOC进行解码,您应该全部设置。

This应该有希望提供足够的信息,以便您在恢复时对要恢复的任何信息进行编码和解码。

答案 2 :(得分:2)

我还没有完成状态恢复,但我会按照以下思路思考:

  • 应用代表是否先被唤醒?应用程序委托是否有机会走视图控制器?

  • 在等待AppDelegate为其提供上下文时,视图控制器可以暂停吗?

听起来像状态恢复可能是一个特殊情况但是我会探索让视图控制器足够聪明的选项,等待MOC在询问数据之前出现。甚至可能在视图控制器中有一个回滚状态,它们会返回到视图控制器可以等待上下文的位置。

答案 3 :(得分:0)

有一个API,该方法隐藏在UIApplication.h中。

// Register non-View/ViewController objects for state restoration so other objects can reference them within state restoration archives.
// If the object implements encode/decode, those methods will be called during save/restore.
// Obj and identifier must not be nil, or will raise UIRestorationObjectRegistrationException.
// Objects do not need to be unregistered when they are deleted, the State Restoration system will notice and stop tracking the object.
+ (void)registerObjectForStateRestoration:(id<UIStateRestoring>)object restorationIdentifier:(NSString *)restorationIdentifier NS_AVAILABLE_IOS(7_0);

在您的AppDelegate.m中放置:

[UIApplication registerObjectForStateRestoration:self.persistentContainer.viewContext restorationIdentifier:@"MyViewContext"];

要允许注册该对象,请创建实现协议的NSManagedObjectContext类别(不需要任何方法)。

@interface NSManagedObjectContext (XXX) <UIStateRestoring> 

在需要托管对象属性的视图控制器中(例如DetailViewController),对对象和上下文进行编码。然后在解码中使用上下文找到对象。

- (void)encodeRestorableStateWithCoder:(NSCoder *)coder{
    [super encodeRestorableStateWithCoder:coder];
    NSManagedObjectID *objectID = self.detailItem.objectID;
    [coder encodeObject:objectID.URIRepresentation forKey:@"detailItem"];
    [coder encodeObject:self.detailItem.managedObjectContext forKey:@"context"];
}

- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
    [super decodeRestorableStateWithCoder:coder];
    NSManagedObjectContext *context = [coder decodeObjectForKey:@"context"];
    NSURL *objectURI = [coder decodeObjectForKey:@"detailItem"];
    NSManagedObjectID *objectID = [context.persistentStoreCoordinator managedObjectIDForURIRepresentation:objectURI];
    NSManagedObject *detailItem = [context objectWithID:objectID];
    self.detailItem = detailItem;
}

这里发生的是通过注册上下文对象以进行还原,当其他视图控制器对上下文进行编码时,它们实际上将对相同上下文的引用进行编码,而不是将上下文作为数据。因此,在还原期间,当视图控制器解码上下文引用时,它将获得在应用程序委托中注册的引用。显然,注册必须在解码之前进行,否则将找不到它。

答案 4 :(得分:-2)

我从NSScreencast学到了一种非常简洁的方法来设置核心数据堆栈。基本上,您可以在不选择&#34;使用核心数据的情况下启动Xcode项目。选项。然后添加一个单例类,它是您的数据模型。因此,要获得主要的MOC,您需要[[DataModel sharedModel] mainContext]。我发现在App Delegate中倾倒所有东西要干得多。

我从未以这种方式使用它,但我想在你的情况下你也可以在你的视图控制器中执行此操作:

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

答案 5 :(得分:-2)

我的解决方案是使视图控制器默认使用全局共享MOC。在大多数情况下,此上下文将是预期的上下文,如果您需要通过任何其他MOC,则完全可以这样做。

这种方法证实了Apple的“传递接力棒”方法以及既方便又兼容状态恢复。