バックグラウンドでkillされたアプリを元の状態に復元する（State Restoration）

#State Restorationとは
http://cockscomb.hatenablog.com/entry/state-restoration
>iOS の State Restoration という機能は、アプリを元の状態に復元するための仕組みを提供します。メモリが不足してバックグラウンドのアプリが終了した後、次に起動したときに、アプリを元の状態に戻すことができます。地図で見つけた道順や、検討中の商品、または入力中のメッセージや、さっき見ていた web ページなど、そういったものをそのまま残しておくことができます。もしこのような復元が行われなければ、ユーザーはそれまでのコンテキストを失い、せっかく見つけたものを見失ったり、大切な言葉を忘れてしまったり、とてもがっかりしてしまうに違いありません。

上記の通り、バックグラウンドに入っていつの間にかkillされてしまったアプリをUINavigationのツリーも含めて復元します。
細かい検索条件を指定して一覧画面に遷移するようなアプリだと実装されていると嬉しい、かも。
例）せっかく指定した検索条件をまた入力…などのストレスを無くす。

#サンプル実装
State Restorationを実装する必要の無い構造ですが、簡単な実装方法サンプルということで載せておきます。

##画面構成
今回は以下のような画面構成でState Restorationを実装します。

Main.storyboard
![スクリーンショット 2016-08-21 21.56.33.png](https://qiita-image-store.s3.amazonaws.com/0/104665/7d7130a3-c966-14fe-8484-a13ccd8d3ab4.png)
Next.storyboard
![スクリーンショット 2016-08-21 22.20.28.png](https://qiita-image-store.s3.amazonaws.com/0/104665/b96abde5-0a5f-291d-f412-684963299b70.png)

Mainの`ViewController`で入力した文字と、`NextView`ボタンの`enabled`を保存・復元します。
Nextの`NextViewController`では、`ViewController`から渡されたパラメータ(入力文字)を表示します。
また、`NextViewController`でも表示文字の保存・復元を行います。

###実行イメージ
一度終了したアプリの画面を復元します。
![restore_gimp.gif](https://qiita-image-store.s3.amazonaws.com/0/104665/763ca25e-0f2c-1a42-ff30-b054995c854b.gif)

##保存と復元の許可設定
下記の２つのメソッドを実装し、保存と復元の許可を行います。

```swift
optional func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool
optional func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool
```
###実装
保存された時と復元される時でアプリケーションのバージョンが異なるような場合はここで制御を行うらしいですが、今回は実装しません。

```swift:AppDelegate
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    
    func application(application: UIApplication, willFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
        // このメソッドの後、shouldRestoreApplicationState →viewDidLoad() →decodeRestorableStateWithCoder →init(coder:) →didFinishLaunchingWithOptions の流れで実行される
        // そのため、復元の際にはここが初期化メソッドとなる
        // didFinishLaunchingWithOptionsと同様の処理を実行する（dispatch_once_tなどを使用？）
        print(String(AppDelegate) + ":" + #function)
        return true
    }

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        print(String(AppDelegate) + ":" + #function)
        return true
    }

    // MARK: Restore
    func application(application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
        print(String(AppDelegate) + ":" + #function)
        
        // 保存許可
        return true
    }
    
    func application(application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
        print(String(AppDelegate) + ":" + #function)
        
        // 復元許可
        return true
    }
}
```
##保存と復元の対象を設定
Storyboardで`Restoration ID`を設定します。（コードでも出来ますが省略します）
入れ子の場合、親となる`UIViewController`全てにこの設定が必要になります。
例えば、親の`UINavigationController`に設定し忘れると子が保存されなくなります。
子の場合は、実装必須ではないので、欲しければ実装ということになります。（今回で言えば`NextViewController`）

###実装
![スクリーンショット 2016-08-21 21.45.16.png](https://qiita-image-store.s3.amazonaws.com/0/104665/d3bff394-f7ad-0615-4a33-3715383c83b9.png) ![スクリーンショット 2016-08-21 21.45.31.png](https://qiita-image-store.s3.amazonaws.com/0/104665/a875c784-9644-dbdd-8422-10b6948f97d4.png)

![スクリーンショット 2016-08-22 00.02.39.png](https://qiita-image-store.s3.amazonaws.com/0/104665/0f782dc4-1faa-c952-cc31-9410755f8012.png) ![スクリーンショット 2016-08-22 00.01.35.png](https://qiita-image-store.s3.amazonaws.com/0/104665/267c8087-92db-44d3-5cdd-9b13eb4a575e.png)



##保存と復元の実装（UIViewController）
下記の二つのメソッドを実装し、保存と復元を行います。
引数を見ればわかる通り、保存と復元の対象となるのは`NSCoding`プロトコルに適合したオブジェクトやプリミティブな値ということになります。

```swift
func encodeRestorableStateWithCoder(_ coder: NSCoder)
func decodeRestorableStateWithCoder(_ coder: NSCoder)
```

###実装
####ViewController
ここでは`NSCoding`プロトコルに適合した独自クラス`RestoreModel`と`nextViewButton`の`enabled`を保存・復元しています。
`RestoreModel`の詳細は次の項目で説明します。

```swift:ViewController
import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var restoreTextField: UITextField!
    @IBOutlet weak var nextViewButton: UIButton!
    
    var restoreModel = RestoreModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print(String(ViewController) + ":" + #function)
        restoreTextField.addTarget(self, action: #selector(ViewController.restoreTextFieldDidChange(_:)), forControlEvents: UIControlEvents.EditingChanged)
        
        // viewDidLoadより後に復元が行われるため、ここでviewに値を設定すると上手くいかない
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        print(String(ViewController) + ":" + #function)
        
        restoreTextField.text = restoreModel.text
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    // MARK: - Restore
    override func encodeRestorableStateWithCoder(coder: NSCoder) {
        super.encodeRestorableStateWithCoder(coder)
        print(String(ViewController) + ":" + #function)
        print("保存")
        
        // keyを指定して保存
        // restoreModelのencodeWithCoderが実行される
        coder.encodeObject(restoreModel, forKey:"restoreModel")
        coder.encodeBool(nextViewButton.enabled, forKey:"nextViewButton.enabled")
    }
    
    override func decodeRestorableStateWithCoder(coder: NSCoder) {
        super.decodeRestorableStateWithCoder(coder)
        print(String(ViewController) + ":" + #function)
        print("復元")
        
        // 復元
        // restoreModelのinit(coder:)が実行される
        if let restore = coder.decodeObjectForKey("restoreModel") as? RestoreModel {
            restoreModel = restore
        }
        if coder.containsValueForKey("nextViewButton.enabled") {
            nextViewButton.enabled = coder.decodeBoolForKey("nextViewButton.enabled")
        }
    }
    
    // MARK: - Action
    @IBAction func onTouchedNextViewButton(sender: UIButton) {
        let viewContrller = UIStoryboard(name: "Next", bundle: nil).instantiateViewControllerWithIdentifier("NextViewController") as! NextViewController
        viewContrller.restoreModel = restoreModel
        self.navigationController?.pushViewController(viewContrller, animated: true)
    }
    
    func restoreTextFieldDidChange(textField:UITextField){
        restoreModel.text = textField.text!
        nextViewButton.enabled = textField.text?.characters.count > 0
    }
}

// MARK: - UITextFieldDelegate
extension ViewController: UITextFieldDelegate {
    func textFieldShouldReturn(textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}
```
####NextViewController
`ViewController`からの遷移先の画面でも保存と復元を行います。

```swift:NextViewController
import UIKit

class NextViewController: UIViewController {
    @IBOutlet weak var restoreLabel: UILabel!
    var restoreModel = RestoreModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print(String(NextViewController) + ":" + #function)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        print(String(NextViewController) + ":" + #function)
        
        restoreLabel.text = restoreModel.text
    }
    
    // MARK: - Restore
    override func encodeRestorableStateWithCoder(coder: NSCoder) {
        super.encodeRestorableStateWithCoder(coder)
        print(String(NextViewController) + ":" + #function)
        print("保存")
        
        // keyを指定して保存
        // restoreModelのencodeWithCoderが実行される
        coder.encodeObject(restoreModel, forKey:"restoreModel")
    }
    
    override func decodeRestorableStateWithCoder(coder: NSCoder) {
        super.decodeRestorableStateWithCoder(coder)
        print(String(NextViewController) + ":" + #function)
        print("復元")
        
        // 復元
        // restoreModelのinit(coder:)が実行される
        if let restore = coder.decodeObjectForKey("restoreModel") as? RestoreModel {
            restoreModel = restore
        }
    }
}

```

##保存と復元の実装（NSCoder）
独自クラスを保存・復元させるには`NSCoding`プロトコルで定義されている、以下の二つのメソッドを実装する必要があります。

```swift
func encodeWithCoder(aCoder: NSCoder)
init?(coder aDecoder: NSCoder)
```

###実装
`encodeWithCoder:aCoder`でシリアライズして、`init?:aDecoder`でデシリアライズします。

```swift
import UIKit

// NSCodingを継承する
class RestoreModel: NSObject, NSCoding {
    var text = ""
    
    override init() {
        super.init()
    }
    
    // MARK: - NSCoding
    func encodeWithCoder(aCoder: NSCoder) {
        print(String(RestoreModel) + ":" + #function)
        
        // keyを指定して保存
        aCoder.encodeObject(text, forKey: "text")
    }
    
    required init(coder aDecoder: NSCoder) {
        print(String(RestoreModel) + ":" + #function)
        
        // 復元
        text = aDecoder.decodeObjectForKey("text") as! String
    }
}
```

#デバッグ手順
色々やり方はあるのかもしれませんが、今回はシミュレータのDebugを再実行させる方法で確認しました。

0. シミュレータを起動
1. シミュレータをHome画面へ（Hardware>Home）
2. シミュレータを停止
3. シミュレータを起動

2.を実施するとデータ保存のメソッド`encodeRestorableStateWithCoder:coder`が実行されます。
4.を実施するとデータ復元のメソッド`decodeRestorableStateWithCoder:coder`が実行されます。


#参考
http://cockscomb.hatenablog.com/entry/state-restoration
http://ninjinkun.hatenablog.com/entry/2012/10/20/122257
http://troutdev.blogspot.jp/2014/12/uistaterestoring-in-swift.html

#最後に
なんとなく実装してみましたが、処理が煩雑になりそうなので注意が必要です。