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

标签 ios core-data ios6 state-restoration

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

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

这似乎与 State Restoration 不太吻合。我唯一能想到的就是在 viewControllerWithRestorationIdentifierPath:coder: 的实现中直接从我的 App Delegate 检索 MOC。事实上,Apple 开发人员在观看 WWDC session 时似乎也做了类似的事情。

这是最好的/唯一的方法吗?状态恢复是否有效地打破了接力棒,至少对于恢复的 View Controller ?

最佳答案

要熟悉状态恢复,我强烈推荐 WWDC 2013 session What's New in State Restoration .虽然状态恢复是在一年前在 iOS 6 中引入的,但 iOS 7 带来了一些显着的变化。
向前传递
使用“传递接力棒”的方法,在某些时候根 NSManagedObjectContext创建了一个 NSPersistentStoreCoordinator被附上。上下文被传递给 View Controller ,随后的 subview Controller 依次传递给根上下文或子上下文。
例如,当用户启动应用程序时,root NSManagedObjectContext创建并传递给根 View Controller ,该 Controller 管理一个 NSFetchedResultsController .当用户在 View Controller 中选择一个项目时,会创建一个新的细节 View Controller 和一个 NSManagedObjectContext实例传入。
Passing the Managed Object Context Baton
保存和恢复状态
状态恢复以对在 View Controller 中使用 Core Data 的应用程序很重要的方式改变了这一点。如果用户在细节 View Controller 上并将应用程序发送到后台,系统会创建一个恢复文件,其中包含有助于重建他们离开时可见状态的信息。关于整个 View Controller 链的信息被写出,当应用程序重新启动时,这用于重建状态。
State Restoration
发生这种情况时,它不会使用任何自定义初始化程序、转场等。 UIStateRestoring协议(protocol)定义了用于编码和解码状态的方法,允许某种程度的定制。符合 NSCoding 的对象可以存储在恢复文件中,并且在 iOS 7 状态恢复扩展到模型对象和数据源。
状态恢复旨在仅存储重建应用程序可见状态所需的信息。对于 Core Data 应用程序,这意味着在正确的持久存储中存储定位对象所需的信息。
从表面上看,这似乎很简单。在 View Controller 管理 NSFetchedResultsController 的情况下这可能意味着存储谓词和排序描述符。对于显示或编辑单个托管对象的详细 View Controller ,托管对象的 URI 表示将添加到状态恢复存档中:

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

    [coder encodeObject:[objectID URIRepresentation] forKey:kManagedObjectKeyPath];
    [super encodeRestorableStateWithCoder:coder];
}
当状态恢复时 UIStateRestoring 方法 -decodeRestorableStateWithCoder:被调用以从存档信息中恢复对象:
  • 从恢复文件中解码 URI。
  • 从持久存储协调器获取 URI 的托管对象 ID
  • 从托管对象上下文中获取该托管对象 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];
            }];
        }]; 
    }
    
    这就是事情变得更加复杂的地方。在应用程序生命周期中的点 -decodeRestorableStateWithCoder:被称为 View Controller 将需要正确的 NSManagedObjectContext .
    传递接力棒 vs. 国家恢复:战斗!
    使用“传递接力棒”方法, View Controller 作为用户交互的结果被实例化,并传入一个托管对象上下文。该托管对象上下文连接到父上下文或持久存储协调器。
    在状态恢复期间不会发生。如果您查看“传递接力棒”与状态恢复期间发生的情况的插图,它们可能看起来非常相似-它们确实如此。在状态恢复期间传递数据 - NSCoder代表恢复文件接口(interface)的实例。
    不幸的是NSManagedObjectContext我们需要的信息不能作为恢复文件的一部分存储。 NSManagedObjectContext确实符合 NSCoding ,但是重要的部分没有。 NSPersistentStoreCoordinator没有,所以它不会被持久化。奇怪的是,parentContext NSManagedObjectContext 的属性也不会(我强烈建议就此提交雷达)。
    存储特定的 URL NSPersistentStore实例并重新创建 NSPersistentStoreCoordinator在每个 View Controller 中似乎是一个有吸引力的选择,但结果将是每个 View Controller 的不同协调器 - 这可能很快导致灾难。
    因此,虽然状态恢复可以提供在 NSManagedObjectContext 中定位实体所需的信息。 ,它不能直接提供重新创建上下文本身所需的内容。
    那么接下来呢?
    最终在 View Controller 的 -decodeRestorableStateWithCoder: 中需要什么是 NSManagedObjectContext 的一个实例它具有与编码状态时相同的出身。它应该具有祖先上下文和持久存储的相同结构。
    状态恢复从 UIApplicationDelegate 开始,其中几个委托(delegate)方法被调用作为恢复过程的一部分( -application:willFinishLaunchingWithOptions:-application:shouldRestoreApplicationState:-didDecodeRestorableStateWithCoder:-application:viewControllerWithRestorationIdentifierPath:coder: )。每一个都是从一开始就自定义恢复过程并传递信息的机会 - 例如附上 NSManagedObjectContext实例作为对 NSCoder 的关联对象引用用于恢复。
    如果应用程序委托(delegate)对象负责创建根上下文,则一旦启动过程完成(有或没有状态恢复),该对象就可以在整个 View Controller 链中下推。每个 View Controller 将传递适当的 NSManagedObjectContext它的 subview Controller 的实例:
    @implementation UIViewController (CoreData)
    
    - (void) setManagedObjectContext:(NSManagedObjectContext *)context {
        [[self childViewControllers] makeObjectsPerformSelector:_cmd withObject:context];
    }
    
    @end
    
    每个提供自己实现的 View Controller 都会创建自己的子上下文。这还有其他优点 - 任何让托管对象上下文的用户对其更改使用react的方法都可以更容易地异步创建上下文。创建上下文本身既快速又轻量,但将持久存储添加到根上下文可能非常昂贵,并且不应允许在主队列上运行。许多应用程序在应用程序委托(delegate)方法中的主队列上执行此操作,并最终在打开存储文件的时间过长或需要迁移时被操作系统杀死。在另一个线程上添加持久存储,然后在准备好时将上下文发送到使用它的对象可以帮助防止这些类型的问题。
    另一种方法可能是利用 View Controller 中的响应者链。在状态恢复期间, View Controller 可以遍历响应者链以找到下一个 NSManagedObjectContext向上链,创建一个子上下文,然后使用它。使用非正式协议(protocol)实现这一点很简单,并且会产生灵活且适应性强的解决方案。
    非正式协议(protocol)的默认实现会在响应者链上进一步上升:
    @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提供替代实现。这包括应用程序委托(delegate),它确实参与了响应者链。使用上面的非正式协议(protocol),如果 View 或 View Controller 调用 -managedObjectContext默认实现会一直到应用程序委托(delegate)返回结果,除非沿途的其他对象提供了非 nil 结果。
    您还可以选择使用具有状态恢复的恢复类工厂来在恢复期间重建托管对象上下文链。
    这些解决方案并不适用于所有应用程序或情况,只有您可以决定什么适合您。

    关于ios - 如何最好地处理核心数据 + iOS 状态恢复?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17981833/

    相关文章:

    ios - Firebase 通知可以与 APN 共存吗?

    ios - iPad 上的视频有字母装箱但在桌面上没有

    ios - 从应用商店更新后,应用程序因 SQLite 错误而崩溃

    uitableview - UITableView iOS6底部的UIRefreshControl?

    iPhone Retina 显示屏与 Apple App Store 上传

    objective-c - iOS 使用 navigationController popToRootViewControllerAnimated

    ios - 如何制造海星中所见的水?

    ios - 我无法从 PFFile 获取图像

    iOS:将数据存储在内存中而不是磁盘上

    iphone - 两个不同的 ManagedObjects 和一个 SQLite 数据库?