保存和恢复App的状态

前言

为什么翻译这系列文章

概述

ViewController在App状态的保存和恢复过程中扮演者重要的角色。状态保存会在App挂起之前记录App的配置,在其重新启动的时候可以恢复其配置信息。将App恢复到之前的配置可以为用户节省时间并提供更好的用户体验。
状态的保存和恢复过程几乎都是自动执行的,但是你需要告知iOS系统,你的App的那一部分将被保存,下面的步骤展示了如何保存你的App的ViewController状态:

  • (必选)将恢复标识赋值给你想保存状态的ViewController。参考下节。
  • (必须)告知iOS系统如何在启动的时候创建或者定位一个新的ViewController。
  • (可选)对于不同的ViewController,存储原始图控制器的指定的配置数据,以在新的视图控制器中恢复其配置。
    有关App状态的保存和恢复流程的概述,参看:App Programming Guide for iOS

标记ViewController进行保存

UIKit只保留你告诉它保存的视图控制器。每个ViewController都有一个restorationIdentifier属性,默认情况下该属性是nil。为该属性指定一个可用字符串来告知UIKit当前ViewController和它的视图应该被保存。你也可以在Xib中为该属性赋值。
当为某一个ViewController赋值restorationIdentifier属性的时候,要记得为该ViewController视图结构中的上层ViewController也赋值该属性的值。在保存过程中,UIKit从window的根视图控制器开始并走向视图控制器的层次结构。如果某一个ViewController的没有为该属性赋值,那么它以及它的子视图控制器都将被忽略。

选择有效的恢复标识

UIKit使用您的恢复标识字符串来重新创建视图控制器,因此请选择易于识别您的代码的字符串。如果UIKit无法自动创建一个视图控制器,它会要求你创建视图控制器,并为你提供视图控制器及其所有父视图控制器的恢复标识符。这个标识符链接标识视图控制器的恢复路径,以及您如何确定请求那个视图控制器。恢复路径从根视图控制器开始,并包含每个视图控制器直到并包括请求的视图控制器。
恢复标识经常使用ViewController的类名来表示。如果你在多个地方使用了改类,你需要使用更有意义的标识。比如:你可以根据视图控制器管理的数据分配一个字符串。
每个ViewController的恢复路径必须是唯一的。如果一个容器型视图控制器有两个子视图,容器视图必须为每个子视图控制器分配一个唯一的标识。UIKit中的某些容器型视图控制器会自动消除子视图控制器间的歧义,允许你使用相同的标识符标识每个子视图控制器。比如说UINavgationController为每个子视图控制器的标识信息添加其在导航控制器的堆栈位置信息。关于给定视图控制器的更多信息,参考响应的类指南。
关于如何使用恢复标识和恢复路径创建ViewController的更多信息,参考:在启动的时候恢复ViewController。

移除视图控制器组

为了在恢复流程中移除整个视图控制器组,设置父视图控制器的恢复标识为nil。下图展示了在ViewController的视图层级中将恢复标识设置为nil造成的影响。缺乏保存数据会阻止稍后的恢复视图控制器。


从自动保存流程中移除视图控制器

排除一个或多个视图控制器并不会在恢复过程中删除它们。在启动时,app的任何一个视图控制器都会按照默认设置被创建。如下图所示,视图控制器仍会在默认的配置下重建。


加载默认的视图控制器

从自动保存过程中排除视图控制器不会阻止您手动保存视图控制器。
在恢复归档中保存对视图控制器的引用可保留视图控制器及其中状态信息。比如说:第一张图中,导航控制器中有三个子视图控制器,它们的状态会被保存,在恢复期间,应用程序委托可以创建这些视图控制器并将它们推送到导航控制器的堆栈中。

保存视图控制器的视图状态

一些视图有一些额外与自身相关的额外的状态信息,这些信息与它所在的ViewController无关,比如说,你想保存scrollView滚动的位置。但是ViewController只是为scroll view 提供其展示的内容,scroll view 自身担负着存储其显示状态。
为了保存视图的状态,按照如下操作:

  • 为View的restorationIdentifier属性赋值。
  • 为View所在的ViewController的restorationIdentifier赋值
  • 对于tableView和colletionView, 实现UIDataSourceModelAssociation协议,并提供数据源。
    为View赋值restorationIdentifier告知UIKit应当在存储的时候讲View的状态写入归档文件中,当之后ViewController恢复的时候,UIKit同时也恢复那些拥有restorationIdentifier的视图。

在启动的时候恢复视图控制器

在启动时,UIKit尝试恢复到它先前的状态。这时,UIKit要求你的app创建(或者定位)ViewController对象,这些对象中包含了你存储的用户界面。尝试定位视图控制器的时候,UIKit安装如下的顺序进行搜索。

  1. 如果视图控制器拥有一个恢复类,UIKit要求该恢复类提供视图控制器。UIKit调用相关恢复类的viewControllerWithRestorationIdentifierPath:coder:方法来返回视图控制器,如果该方法返回nil,意味着应用程序不想重建这个视图控制器,并且UIKit停止搜索这个控制器。
  2. 如果视图控制器没有恢复类,UIKit要求appdelegate提供这个视图控制器。在没有恢复类的时候,UIKit调用appdelegate的application:viewControllerWithRestorationIdentifierPath:coder:方法来寻找视图控制器。如果这个方法返回nil,UIKit视图隐式的查找视图控制器。
  3. 如果视图控制器拥有正确的还原路径,则UIKit将使用该对象。如果你的app在启动的时候创建ViewController(不管是通过编码的形式还是通过Storyboard),并且这些ViewController有用恢复标识,UIKit隐式的基于恢复路径发现它们。
  4. 如果ViewController之前是通过Storyboard文件加载的,UIKit使用被保存的Storyboard信心来定位并创建它。UIKit将有关视图控制器的Storyboard信息保存在恢复归档中。在恢复时,如果视图控制器没用被通过其他的方式找到,UIKit使用这些信息来定位同一个Storyboard文件并且实例化响应的ViewController。
    将恢复类赋值给视图控制器可以避免UIKit通过隐式的方式查找视图控制器。使用恢复类可以更好的控制你是否真的想创建视图控制器。比如说,如果你的类确定不应该创建视图控制器,你的viewControllerWithRestorationIdentifierPath:coder:方法可以返回nil。当没有恢复类时,UIKit会尽其所能找到或创建视图控制器并将其恢复。
    当使用恢复类的时候,你的viewControllerWithRestorationIdentifierPath:coder:方法应当创建一个新的实例,执行最小的初始化并返回结果。下面代码展示了一个例子,如何使用该方法从Storyboard中加载视图控制器,因为视图控制器通常从Storyboard中被加载,这个方使用UIStateRestorationViewControllerStoryboardKey键来从归档文件中获取Storyboard。请注意,此方法不会尝试配置视图控制器的数据字段。当视图控制器的状态被解码时,稍后发生该步骤。
+ (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数组中的最后一项并将其分配给您的视图控制器。
对于在启动时从应用程序主要故事板文件创建的对象,请勿为每个对象创建新实例。让UIKit隐式查找这些对象或使用应用程序的viewControllerWithRestorationIdentifierPath:coder:您的应用程序委托的方法来查找现有对象。

对视图控制器的状态进行编码和解码

对于每一个要保存的对象,UIKit调用对象的encodRestorableStateWithCoder:方法来保存它的状态。在恢复过程中,UIKit调用对应的decodeRestorableStateWithCoder:方法来解码状态并应用到该对象上。这两个方法的实现都是可选的,但推荐你实现它们。你可以使用它们来存储和恢复下面信息:

  • 对所有显示数据的引用
  • 对于容器型视图控制器,引用其子视图控制器。
  • 当前选择的信息。
  • 对于用户可配置的视图控制器,当前视图的配置信息。
    在endcode 和 decode方法中,你可以编码任何支持coder的数据类型和对象。对于任何希望被编码的视图或者视图控制器,对象必须实现NSCoding协议并使用该协议的方法来写入它的状态。对于视图和视图控制器,编码器不使用NSCoding协议来存储对象的状态,相反,编码器存储对象的恢复标识并将它添加到存储对象列表,这会导致该对象的encoderRestorableStateWithCoder:方法被调用。
    ViewController的encodeRestorableStateWithCoder :decodeRestorableStateWithCoder :方法必须在其实现中调用其super方法,调用super方法让父类可以保存和恢复额外的信息。下面代码实现了一个简单的例子,使用这些方法来保存ViewController的一个数值信息。
- (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];
}

编码器对象在编码和解码的过程中不共享。每个可保存状态的对象保留这它自己的编码器对象。使用独立的编码器意味着你不必担心键之间的命名冲突。但是不要使用UIApplicationStateRestorationBundleVersionKey, UIApplicationStateRestorationUserInterfaceIdiomKe, 和 UIStateRestorationViewControllerStoryboardKey作为你的键。这些键被UIKit对象用来存储一些你的ViewController的额外信息。
关于更多实现编码和解码的方法的信息,参考UIViewController Class Reference

保存和恢复ViewController的一些建议

在你给ViewController添加对状态的保存和恢复时,考虑以下几个原则:

  • 记住你可能不需要存储所有的VIewController。在某些情况下,保存ViewController是没有意义的,比如说,如果你的APP正在显示一个变化操作,则可能需要取消操作并将应用程序的状态恢复到上一个屏幕。在这种情况下,你不会保存要求输入新密码信息的视图控制器。
  • 避免在恢复过程中交换视图控制器类。状态保存系统编码它保存的视图控制器的类。在恢复过程中,如果你的应用程序返回了一个与原先的类不相符的类,系统不会要求视图控制器解码任何状态信息。因此,换掉一个旧的视图控制器不会恢复对象的完整性。
  • 状态保存系统希望你按照它们的预期使用ViewController。恢复过程依赖视图控制器的包含关系来重建你的界面。如果您没有正确使用容器视图控制器,保存系统将找不到您的视图控制器。例如,除非在相应的视图控制器之间存在包含关系,否则绝不要在不同的视图中嵌入视图控制器的视图。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容