为了熟悉状态恢复,我强烈推荐WWDC 2013 会议 What's New in State Restoration。虽然一年前在 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];
}
当状态恢复时,调用 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:
调用视图控制器的那一刻,将需要正确 NSManagedObjectContext
的.
传递接力棒与状态恢复:战斗!
使用“传递接力棒”方法,视图控制器作为用户交互的结果被实例化,并传入一个托管对象上下文。该托管对象上下文连接到父上下文或持久存储协调器。
在状态恢复期间不会发生。如果您查看“传递接力棒”与状态恢复期间发生的情况的插图,它们可能看起来非常相似 - 确实如此。在状态恢复期间,数据被传递——NSCoder
代表恢复档案接口的实例。
不幸的是,NSManagedObjectContext
我们需要的信息不能作为恢复档案的一部分存储。NSManagedObjectContext
确实符合NSCoding
,但重要部分不符合。NSPersistentStoreCoordinator
没有,所以它不会被持久化。奇怪的是,parentContext
an 的属性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
并且每个提供它自己的实现的视图控制器都会创建它自己的子上下文。这还有其他优点 - 任何让托管对象上下文的用户对其更改做出反应的方法都可以更轻松地异步创建上下文。创建上下文本身是快速且轻量级的,但是将持久存储添加到根上下文可能非常昂贵,并且不应允许在主队列上运行。许多应用程序在应用程序委托方法中的主队列上执行此操作,并且在打开存储的文件时间过长或需要迁移时最终被操作系统杀死。在另一个线程上添加持久性存储,然后在准备好时将上下文发送给使用它的对象可以帮助防止此类问题。
另一种方法可能是利用视图控制器中的响应者链。在状态恢复期间,视图控制器可以遍历响应者链以找到链上的下一个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
默认实现将一直到应用程序委托以返回结果,除非沿途有其他对象提供非零结果。
您还可以选择使用具有状态恢复的恢复类工厂来在恢复期间重建托管对象上下文链。
这些解决方案并不适用于所有应用程序或情况,只有您可以决定什么对您有用。