APP在某种情况下会被系统终止,然而再次启动APP时,之前被终止前的状态就丢失了。比如之前正在输入表单数据,突然APP被系统终止了,再次启动APP时就需要重新输入表单数据了。APP状态恢复可以解决这个问题,让APP再次启动时恢复到上次编辑时到状态。文档如是说:
Preserving Your App's UI Across Launches,Return your app to its previous state after it is terminated by the system.
通常app被终止是因为系统要回收内存,为了让正在启动的app获得内存空间。也有可能因为app的行为异常或者长时间没有响应时间。具体文档是这么说的:
The system usually terminates apps so that it can reclaim memory and make room for other apps being launched by the user, but the system may also terminate apps that are misbehaving or not responding to events in a timely manner.
实现过程
1.保存状态时,调用shouldSaveApplicationState方法,判断是否要保存app状态。 2.如果需要保存,从根控制器开始,调用encodeRestorableState方法保存数据。如果这个控制器没有restorationIdentifier标识,就不会调用此方法。 3.在再次启动时,调用shouldRestoreApplicationState方法,判断是否需要恢复。 4.如果需要,则调用有restorationIdentifier的控制器的decodeRestorableState方法恢复数据
注意:
不要再这个方法里保存应该持久化的数据,应该是临时数据,并且可有可无,不影响主要功能的数据。原文如下:
- Do save details about the visual state of views and controls.
- Do save references to child view controllers that you also want to preserve.
- Do save information that can be discarded without affecting the user’s data.
- Don’t include data that’s already in your app’s persistent storage. Instead, include an identifier that you can use to locate that data later.
State preservation isn’t a substitute for saving your app’s data to disk. UIKit can discard state preservation data at its discretion, allowing your app to return to its default state. Use the preservation process to store information about the state of your app’s user interface, such as the currently selected row of a table. Don’t use it to store the data contained in that table.
代码
- 启用状态保存和恢复功能,实现app delegate的shouldSaveApplicationState和shouldRestoreApplicationState两个方法。这两个方法分别决定了保存阶段和恢复阶段是否会发生,有时候保存或者恢复不合适的时候可以返回false
func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
print("shouldSaveApplicationState")
return true
}
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
print("shouldRestoreApplicationState")
return true
}
2.给需要状态保存的控制器设置恢复标识
如果控制器是通过storyboard初始化,在Restoration ID框中填入一个字符串就行,一般是类名。如果是通过代码初始化,在运行时给控制器的RestorationIdentifier属性赋值一个字符串。
3.如果需要的话,在恢复期间重新创建控制器
如果是storyboard初始化,这一步不需要处理,代码初始化则要手动初始化,就是将实现UIViewControllerRestoration代理的一个类赋值给restorationClass属性:
class EditingViewController: UIViewController {
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.restorationClass = EditingViewController.self
self.restorationIdentifier = "EditingViewController"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension EditingViewController: UIViewControllerRestoration {
static func viewController(withRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
let vc = ViewController(nibName: nil, bundle: nil)
return vc
}
}
也可以用APPDelegate的viewControllerWithRestorationIdentifierPath方法替代,在纯代码设置根控制器时用这个方法比较好,因为要替换window的根控制器:
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
let rootVC = ViewController(nibName: nil, bundle: nil)
self.window?.rootViewController = rootVC
self.window?.makeKeyAndVisible()
return true
}
func application(_ application: UIApplication, viewControllerWithRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
let vc = ViewController(nibName: nil, bundle: nil)
window?.rootViewController = vc//切换根控制器
return vc
}
4.实现保存和恢复的两个方法,分别在保存阶段和恢复阶段调用
override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with: coder)
let text = textField.text
let isFirst: Bool = textField.isFirstResponder
coder.encode(text, forKey: "textFieldText")
coder.encode(isFirst, forKey: "isFirstResponder")
}
override func decodeRestorableState(with coder: NSCoder) {
super.decodeRestorableState(with: coder)
let text = coder.decodeObject(forKey: "textFieldText") as? String
let isFirst = coder.decodeBool(forKey: "isFirstResponder")
textField.text = text
if isFirst {
textField.becomeFirstResponder()
}
}
demo地址:github.com/Wejua/Demos…
注意
- 在任务管理器里手动杀死APP不会触发Restoration
- 如果是利用纯代码恢复window的根控制器,要记得把restoration阶段初始化的控制器设置为根控制器,并且要将设置根控制器的代码移动到willFinishLaunchingWithOptions,代码如上