[TOC=5]
* * * * *
>原文链接 :[Preserving and Restoring State](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html#//apple_ref/doc/uid/TP40007457-CH28-SW1)
视图控制器在状态保存和恢复过程中起着重要的作用。 在挂起之前状态保存记录您的应用程序的配置,以便在后续应用程序启动时恢复配置。 将应用程序返回到以前的配置为用户节省了时间,并提供了更好的用户体验。
保存和恢复过程大部分是自动的,但是你需要告诉iOS你的应用程序的哪些部分可以保存。
保留 app 的视图控制器的步骤如下:
* (必需)将恢复标识符分配给要保留其配置的视图控制器; 请参阅[ Tagging View Controllers for Preservation](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html#//apple_ref/doc/uid/TP40007457-CH28-SW2)。
* (必需)告诉iOS如何在启动时创建或定位新的视图控制器对象; 请参阅[ Tagging View Controllers for Preservation](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html#//apple_ref/doc/uid/TP40007457-CH28-SW5)。
* (可选)对于每个视图控制器,存储返回该视图控制器到其原始配置所需的任何特定配置数据;请参阅[ Encoding and Decoding Your View Controller’s State](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html#//apple_ref/doc/uid/TP40007457-CH28-SW6)。
有关保存和恢复过程的概述,请参阅[ App Programming Guide for iOS](https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007072) 。
### 为了保存标记视图控制器
UIKit 只保留您告诉它保存的视图控制器。 每个视图控制器都有一个 `restoreIdentifier` 属性,其默认值为 `nil` 。 将该属性设置为有效的字符串将告诉 UIKi t应该保留视图控制器及其视图。 您可以以编程方式或在故事板文件中分配恢复标识符。
分配恢复标识符时,请记住,视图控制器层次结构中的所有父视图控制器也必须具有恢复标识符。 在保存过程中,UIKit从窗口的根视图控制器开始,并遍历视图控制器层次结构。 如果该层次结构中的视图控制器没有恢复标识符,则视图控制器及其所有子视图控制器和呈现的视图控制器将被忽略。
#### 选择有效的恢复标识符
UIKit 使用您的恢复标识符字符串来重新创建视图控制器,因此选择易于识别的字符串。如果 UIKit 不能自动创建一个视图控制器,它要求你创建它,为你提供视图控制器和所有父视图控制器的恢复标识符。此标识符链表示视图控制器的恢复路径,以及如何确定请求的视图控制器。恢复路径从根视图控制器开始,包括所有视图控制器,直至包括所请求的视图控制器。
恢复标识符通常只是视图控制器的类名称。 如果您在许多地方使用相同的类,则可能需要分配更有意义的值。 例如,您可以根据视图控制器管理的数据分配一个字符串。
每个视图控制器的恢复路径必须是唯一的。如果容器视图控制器有两个子元素,那么容器必须为每个子节点指定一个惟一的恢复标识符。在 UIKit 中,一些容器视图控制器会自动消除其子视图控制器的歧义,允许您为每个子节点使用相同的恢复标识符。
例如,UINavigationController 根据它在导航堆栈中的位置给每个子元素添加信息。有关给定视图控制器的行为的更多信息,请参见相应的类引用。
有关如何使用恢复标识符和恢复路径来创建视图控制器的更多信息,请参阅[ Restoring View Controllers at Launch Time](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html#//apple_ref/doc/uid/TP40007457-CH28-SW5)。
#### 排除视图控制器组
要从还原过程中排除整个视图控制器组,请将父视图控制器的还原标识符设置为 `nil` 。 图7-1显示了将视图控制器层次结构中的恢复标识符设置为 `nil` 的影响。 缺少保存数据可以防止视图控制器稍后被恢复。
###### 图 7-1 从自动保存过程中排除视图控制器
![](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/Art/state_vc_caveats_2x.png)
排除一个或多个视图控制器在随后的恢复过程中不会删除它们全部。在启动时,您的应用程序的默认设置中的任何一个视图控制器仍然被创建,如图 7-2 所示 。
![](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/Art/state_vc_caveats_2_2x.png)
从自动保存过程中排除视图控制器不会阻止您手动保存视图控制器。 在恢复归档中保存对视图控制器的引用可保留视图控制器及其状态信息。 例如,如果图 7-1 中的应用程序委托保存了导航控制器的三个子项,则它们的状态将被保留。 在还原期间,app delegate 可以重新创建这些视图控制器并将其推送到导航控制器的堆栈中。
#### 保留视图控制器的视图
有些视图具有与视图相关的附加状态信息,而不是与父视图控制器相关。例如,滚动视图具有可能想要保存的滚动位置。当视图控制器负责提供滚动视图的内容时,滚动视图本身负责保存它的视觉状态。
要保存视图的状态,请执行以下操作:
* 将一个有效字符串分配给视图的 `restorationIdentifier` 属性。
* 使用具有有效恢复标识符的视图控制器中的视图。
* 表视图和视图集合,分配一个数据来源,遵守 `UIDataSourceModelAssociation` 协议。
将一个恢复标识符分配给一个视图告诉UIKit它应该将视图的状态写入保存存档。稍后恢复视图控制器时,UIKit 还会恢复任何具有恢复标识符的视图的状态。
### 在启动时恢复视图控制器
在启动时,UIKit 会尝试将您的应用恢复到之前的状态。 那时,UIKit 会要求您的应用程序创建(或定位)包含您保存的用户界面的视图控制器对象。 尝试定位视图控制器时,UIKit 按以下顺序搜索:
1. **如果视图控制器有恢复类,UIKit会要求该类提供视图控制器**。 UIKit调用恢复相关类的 `viewControllerWithRestorationIdentifierPath:coder:` 方法来检索视图控制器。 如果该方法返回 `nil` ,则假定应用程序不想重新创建视图控制器,并且 UIKit 停止查找它。
2. **如果视图控制器没有恢复类,则 UIKit 会要求 app delegate 提供视图控制器**。 UIKit 调用 app delegate 的 `application:viewControllerWithRestorationIdentifierPath:coder:` 方法来查找没有恢复类的视图控制器。 如果该方法返回 nil,则UIKit将尝试隐式查找视图控制器。
3. **如果具有正确还原路径的视图控制器已经存在,则UIKit将使用该对象**。 如果您的应用程序在启动时创建视图控制器(以编程方式或通过从故事板加载视图控制器),并且这些视图控制器具有恢复标识符,则UIKit会根据其恢复路径隐式查找它们。
4. **如果视图控制器最初是从一个故事板文件加载的,UIKit 会使用保存的故事板信息来定位和创建它**。UIKit在恢复存档中保存关于视图控制器的故事板的信息。在恢复时,UIKit 使用这些信息来定位相同的故事板文件,如果视图控制器没有被其他方法发现,则实例化相应的视图控制器。
为视图控制器分配一个恢复类可以防止 UIKit 隐式地搜索该视图控制器。 使用恢复类可以更好地控制是否真的要创建视图控制器。 例如,如果您的类确定不应该重新创建视图控制器,则您的 `viewControllerWithRestorationIdentifierPath:coder:` 方法可以返回 `nil` 。 当没有恢复类时,UIKit 会尽其所能找到或创建视图控制器并将其恢复。
当使用恢复类时,您的 `viewControllerWithRestorationIdentifierPath:coder:` 方法应该创建一个新的类实例,执行最小化的初始化,并返回结果对象。 清单 7-1 显示了一个如何使用这个方法从故事板加载视图控制器的例子。 由于视图控制器最初是从故事板加载的,因此此方法使用 `UIStateRestorationViewControllerStoryboardKey` 键从档案中获取故事板。 请注意,此方法不会尝试配置视图控制器的数据字段。当视图控制器的状态被解码时,这个步骤就会发生。
###### 清单7-1在恢复期间创建一个新的视图控制器
~~~
+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
coder:(NSCoder *)coder
{
MyViewController* vc;
UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb) {
vc = (PushViewController*)[sb instantiateViewControllerWithIdentifier:@"MyViewController"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [MyViewController class];
}
return vc;
}
~~~
重新分配恢复标识符和恢复类是手动重新创建视图控制器时采用的良好习惯。 恢复恢复标识符的最简单方法是获取 `identifierComponents` 数组中的最后一项,并将其分配给您的视图控制器。
对于在启动时从app的主故事板文件中创建的对象,不要创建每个对象的新实例。让 UIKit 隐式查找这些对象,或使用应用程序的 `viewControllerWithRestorationIdentifierPath:coder:` 方法来查找现有对象。
### 编码和解码您的视图控制器的状态
对于要保存的每个对象,UIKit 都会调用对象的 `encodeRestorableStateWithCoder:` 方法,使其有机会保存其状态。 在恢复过程中,UIKit 调用匹配的 `decodeRestorableStateWithCoder:` 方法来解码该状态并将其应用于对象。 这些方法的实现是可选的,但对于视图控制器来说,建议使用。 您可以使用它们来保存和恢复以下类型的信息:
* * 对正在显示的任何数据的引用(不是数据本身)
* 对于容器视图控制器来说,它的子视图控制器的引用
* 关于当前选择的信息
* 对于具有用户可配置视图的视图控制器,关于该视图的当前配置的信息。
在您的编码和解码方法中,您可以对编码器支持的对象和任何数据类型进行编码。 对于除视图和视图控制器以外的所有对象,对象必须采用 NSCoding 协议,并使用该协议的方法来写入其状态。 对于视图和视图控制器,编码器不使用 NSCoding 协议来保存对象的状态。相反,编码器保存对象的恢复标识符并将其添加到可保存对象的列表中,这将导致对象的 `encodeRestorableStateWithCoder:` 方法被调用。
视图控制器的 `encodeRestorableStateWithCoder:` 和 `decodeRestorableStateWithCoder:` 方法必须在其实现的某个时刻实现父类调用。 父类调用让父类有机会保存和恢复任何附加信息。 清单 7-2 显示了这些方法的一个示例实现,它们保存用于标识指定视图控制器的数字值。
###### 清单 7-2 编码和解码视图控制器的状态
~~~
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[super encodeRestorableStateWithCoder:coder];
[coder encodeInt:self.number forKey:MyViewControllerNumber];
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
[super decodeRestorableStateWithCoder:coder];
self.number = [coder decodeIntForKey:MyViewControllerNumber];
}
~~~
编码器对象在编码和解码过程中不共享。 每个具有可保存状态的对象都会收到自己的编码器对象。 使用唯一的编码器意味着您不必担心您的密钥之间的命名空间冲突。但是,请不要使用键名称。 UIKit使用这些键来存储关于视图控制器状态的附加信息。
* UIApplicationStateRestorationBundleVersionKey,
* UIApplicationStateRestorationUserInterfaceIdiomKey
* UIStateRestorationViewControllerStoryboard
有关实现视图控制器的编码和解码方法的更多信息,请参阅 [UIViewController Class Reference](https://developer.apple.com/documentation/uikit/uiviewcontroller) 。
### 保存和恢复视图控制器的提示
在视图控制器中添加对状态保存和恢复的支持时,请考虑以下准则:
* **记住,您可能不希望保留所有视图控制器**。在某些情况下,保存视图控制器可能没有意义。例如,如果应用程序显示了更改,您可能希望取消操作,并将应用程序恢复到前一个屏幕。在这种情况下,您将不保存请求新密码信息的视图控制器。
* **在恢复过程中避免更换视图控制器类**。状态保存系统对它保存的视图控制器的类进行编码。在恢复期间,如果您的应用程序返回一个对象,该对象的类与原始对象不匹配(或不是一个子类),系统就不会请求视图控制器解码任何状态信息。因此,将旧的视图控制器替换为完全不同的控制器并不能恢复对象的全部状态。
* **状态保护系统希望您能够按照预期的方式使用视图控制器**。 恢复过程依赖于视图控制器的包含关系来重建您的界面。 如果您没有正确使用容器视图控制器,保存系统将找不到您的视图控制器。 例如,除非在相应的视图控制器之间存在包含关系,否则不要将视图控制器的视图嵌入到不同的视图中。