【Swift】バックグラウンド時にもセキュアなiOSアプリを実装する

## 導入
iOSアプリにはアプリを切り替える時に、最後に表示していた画面を「スナップショット」として保存しておく機能があります。
iPhoneを利用されている方ならご存知かと思いますが、アプリスイッチャーでアプリを切り替える時に、以前表示していた画面が見ることができます。

便利な機能なんですが、これによって個人情報などがスナップショットとして保存され、セキュリティ的な観点では問題になるとのこと。

こんな感じでアプリがバックグラウンド状態にある時に、名前やメアドなどの個人情報がスナップショットとして取得されていることが分かります。
<img width="280" alt="before.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/580445/6ce018b3-0063-f3cf-ba6a-87de08e8dc78.png">



そこで今回は、個人情報をOSのスナップショットから守る実装を書いてみました。

## 実装方法
結論から書くと
**「バックグラウンドに入った時に個人情報を含むオブジェクトをisHidden=trueに設定し、アクティブになったら再表示する」**という処理を書くだけです。
セキュアなアプリを実現すると言うことで、難しい事をするように感じるかもしれませんがシンプルです。

### バックグラウンドでもセキュアなアプリを実装した場合のキャプチャ
<img width="280" alt="after.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/580445/1105a521-26fa-4516-722c-36e78efcc0e7.png">

上記キャプチャをみてお分かりだと思いますが、1枚目のキャプチャと違って、アプリが非アクティブのときに、個人情報に関するスナップショットが取得されていないことが分かります。


### ソースコード
こんな感じです。サンプルアプリは非常にシンプルな実装ですが、アプリが複雑になっても基本的な実装方法は変わらないです。

```swift
import UIKit

// 個人情報を保持する画面にはSecretScreenProtocolを準拠させる
protocol SecretScreenProtocol: AnyObject {
    func protectSecretObject()
    func displaySecretObject()
}

class ViewController: UIViewController {
    
    @IBOutlet weak var labelName: UILabel!
    @IBOutlet weak var labelAddress: UILabel!
    
    let notificationCenter = NotificationCenter.default

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        print("Add Observer")
        notificationCenter.addObserver(self,
                                       selector: #selector(protectSecretObject),
                                       name: UIApplication.willResignActiveNotification,
                                       object: nil)
        notificationCenter.addObserver(self,
                                       selector: #selector(displaySecretObject),
                                       name: UIApplication.didBecomeActiveNotification,
                                       object: nil)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        print("Remove Observer")
        notificationCenter.removeObserver(self,
                                          name: UIApplication.willResignActiveNotification,
                                          object: nil)
        notificationCenter.removeObserver(self,
                                          name: UIApplication.didBecomeActiveNotification,
                                          object: nil)
    }
}

// MARK: - SecretScreenProtocol
extension ViewController: SecretScreenProtocol {
    @objc func protectSecretObject() {
        print("Protect Secret Object")
        // アプリが非アクティブになる時に、個人情報を非表示にする
        labelName.isHidden = true
        labelAddress.isHidden = true
    }
    
    @objc func displaySecretObject() {
        print("Display Secret Object")
        // アプリがアクティブになる時に、個人情報を再表示する
        labelName.isHidden = false
        labelAddress.isHidden = false
    }
}
```

### 解説
ポイントは2つあります。
1. NotificationCenterを使ってアプリの状態を判断する
2. 個人情報を扱うViewControllerにはセキュリティを強化するためのプロトコルを準拠させる

##### 1. NotificationCenterを使ってアプリの状態を判断する
NotificationCenterを活用することで、アプリがバックグラウンドから復帰したときや、アクティブでなくなる瞬間を検知し、表示している画面に対して、個別に処理を実装することができます。

使い方としては、
`ViewDidLoad()`や`viewWillAppear()`などが呼ばれるタイミングで、`addObserver`して、画面に対して通知設定を追加します。
`selector`の中に、通知が来た場合に実施したい処理を書いたメソッドを指定します。
`name`の中にどういったタイミングで通知されたいかを指定します。

逆に画面から離れたらその通知は不要なので`viewWillDisappear()`のタイミングで`removeObserver`をして通知設定を解除します。
※`removeObserver`は、不要になった！という意見もあるようでしたが、僕の場合`removeObserver`を設定しなかったことにより、通知設定が解除されないバグが発生したため、今回は追加しています。詳しい情報をお持ちの方がいたらコメント欄にて教えていただけると嬉しいです。

今回は、アプリがアクティブではなくなる時とアクティブに戻るときを判断したいので、2種類のNotificationCenterを活用します。

|UIApplication|概要|今回行う処理|
|---|---|---|
|willResignActiveNotification |アプリがアクティブでなくなった時に通知される |個人情報を隠す|
|didBecomeActiveNotification  |アプリがアクティブになった時に通知される  |個人情報を表示する|

##### 2. 個人情報を扱うViewControllerにはセキュリティを強化するためのプロトコルを準拠させる
結論から申し上げますと、この処理は必須ではありません。1番で挙げた処理だけを行えば、普通に動きます。
でもプロトコルに準拠させて、実装ミスを減らしたり、実装を統一することができることがSwiftの強みだと思うので、今回はプロトコルを使ってみました。

今回の例でいうと`SecretScreenProtocol`をViewControllerに準拠させることで、強制的にセキュリティを強化するメソッドを実装するようにしています。

## 参考にさせていただいた記事・サイト
https://dev.to/esmeraldibejolli/mobile-security-in-background-app-snapshots-1lmn
https://qiita.com/tosh_3/items/df52802514cc9737e75b