iOS-Preservation And Restoration

本文介绍了,iOS的状态保存和复原流程。

UIKit控制整个状态保存和复原流程,UIKit会在需要的时刻执行保存操作,例如当App切到后台时,此时,UIKit判断App的Views和VCs哪些需要保存,然后将其数据写入加密的硬盘文件中,在App下次运行时,UIKit会检索该文件是否存在,存在则尝试复原App的状态。由于该文件是加密的,所以这一过程只会发生在设备已经解锁以后。

保存过程

在保存过程中,App需要:

  • 告诉UIKit需要状态保存;
  • 告诉UIKit那些VCs和Views需要保存;
  • 将一些相关的对象的数据编码。

UIKit是根据Restoration Identifier是否设置来判断一个ViewController或者View是否要保存的。

iOS-Preservation&Restoration-ID

下面是例子:

iOS-Preservation&Restoration-Views

注意:如果一个VC没有设置Restoration Identifier,则其所有Views也不会被保存。这里,特别注意,如果一个NavigationController没有设置Restoration Identifier,则其所有的VC都不会被保存。

在保存过程中,UIKit识别哪些对象需要保存,并与影响该对象的状态一起写入硬盘,例如VC的Restoration Class。对于每个有Restoration ID的VCs和Views对象,将有一个机会保存一些自身的数据,UIKit会调用VC和View的以下方法:

1
2
3
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
}

对于特殊的TableView和CollectionView,其DataSource必须适配协议:UIDataSourceModelAssociation

对于一些不在VC中,但是又需要保存的状态信息,可以在回调中保存:

1
2
3
- (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder
{
}

流程图:

iOS-Preservation&Restoration-Process

复原过程

在复原过程中,App需要:

  • 告诉UIKit需要状态复原;
  • 提供或者创建UIKit要求的对象;
  • 解码保存过程中编码的对象,用于状态复原。

在App重新启动时,UIKit加载App的Storyboard或Nib文件,调用application:willFinishLaunchingWithOptions:回调,然后尝试复原状态。如果不是从Storyboard或Nib文件,则UIKit会让App提供与之前保存的VCs对应的对象组合,如果一个VC有Restoration class,则直接让其提供VC对象,否则,让AppDelegate提供:

1
2
3
4
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
// 返回值如果为nil,则表示该VC不需要复原
}

Restoration Class定义了UIViewControllerRestoration协议:

1
2
3
4
5
6
7
8
@property (nullable, nonatomic, readwrite, assign) Class<UIViewControllerRestoration> restorationClass NS_AVAILABLE_IOS(6_0);

@protocol UIViewControllerRestoration
+ (nullable UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
// 返回值如果为nil,则UIKit会尝试隐式地搜索该VC,通过已经New的VCs中匹配路径,以及从Storyboard中匹配路径。
}
@end

这两个回调中的viewControllerWithRestorationIdentifierPath的参数,是指VC保存的路径,例如一个TabVC为A,里面第一个Tab是一个NavigationVC为B,其RootVC是C,则路径为:

1
A/B/C,对应上面的数组,注意路径必须是唯一的,且不会产生歧义的。

对于可以从Storyboard里面自动加载的VC,则无需设置Restoration class,否则,最好对其设置Restoration class

VC加载完成后,将会调用方法:

1
2
3
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
}

对于一些不在VC中,但是又需要复原的状态信息,可以在回调中复原:

1
2
3
- (void) application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder
- {
}

注意:无论App成功复原或者复原失败(例如Crash),上述的保存文件都将被丢弃。且,UIKit不会帮助保存VC之间的关系,例如NavigationController中的多个VC。在复原时,最好尽量完整恢复原来的VC结构树。

流程图:

iOS-Preservation&Restoration-Process

例子

AppDelegate设置支持保存和复原

1
2
3
4
5
6
7
8
9
- (BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
{
return YES;
}

- (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
{
return YES;
}

VC支持状态保存和复原

1
2
3
4
5
6
7
8
9
10
11
12
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[super encodeRestorableStateWithCoder:coder];
[coder encodeObject:self.view.backgroundColor forKey:@"BGC"];
}

- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
NSLog(@"decodeRestorableStateWithCoder");
[super decodeRestorableStateWithCoder:coder];
self.view.backgroundColor = [coder decodeObjectForKey:@"BGC"];
}

通过Storyboard

如果是在Storyboard中的VC,直接在面板设置Restoration ID即可。

通过RestorationClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@interface TestVC ()<UIViewControllerRestoration>
@end

@implementation TestVC

- (void)viewDidLoad {
[super viewDidLoad];
self.restorationIdentifier = @"TestVC";
self.restorationClass = [self class];
}

+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder {
MyViewController* vc;
UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb) {
vc = (PushViewController*)[sb instantiateViewControllerWithIdentifier:@"TestVC"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [TestVC class];
}
return vc;
}
@end

通过AppDelegate

1
2
3
4
5
6
7
8
9
10
11
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
NSLog(@"viewControllerWithRestorationIdentifierPath");
UIViewController *vc = nil;
for(NSString *ID in identifierComponents){
if([ID isEqualToString:@"TestVC"]){
vc = [CLWechatViewController sharedInstance];
}
}
return vc;
}

提示

  • 保存版本信息:最好在状态保存和复原中,自己保存一个App版本信息,以便版本变更时,可以进行控制;
  • 不要保存数据信息:数据信息不应该保存在状态信息中,因为状态信息的文件无论是否复原成功都会被删除,这可能会导致异常;
  • 不是所有VC都需要保存:例如密码输入页面可能是不需要保存的,复原的时候,直接复原到登录页面即可;
  • 不要改变VC的Class信息:UIKit是根据Class信息来复原VC的,所有不要去改变VC的Class;
  • 用户强制关闭App时,系统会自动删除保存的状态文件:用户通过多任务管理,关闭App时,以及在启动时Crash两次时,状态文件会被删除,所以,不要通过多任务管理调试,而通过XCode直接Kill App。