【iOS】アプリが終了する直前(タスクキル前)にデータ保存などのクリーンアップ(cleanup)処理を行う【Swift・Xcode】

アプリが終了する直前の処理

iOSアプリの終了直前に、何らかの処理を行いたい場合があるかと思います。
例えば・・・

  • データの保存(キャッシュ)処理
  • 何らかの処理を中断してクリーンアップ処理(API呼び出しの中断、ファイル変換処理の中断など)

こうしたアプリ終了直前の処理は、一般的にどこに書くのが正解でしょうか?

Apple の公式ドキュメントを見ると、AppDelegate の applicationWillTerminate に以下のような記載がありました。

このメソッドは、アプリが終了し、メモリから完全に削除されようとしていることをアプリに通知します。このメソッドを使用して、共有リソースの解放、ユーザーデータの保存、タイマーの無効化など、アプリの最終的なクリーンアップタスクを実行する必要があります。このメソッドの実装には、タスクを実行して戻るのに約5秒かかります。期限が切れる前にメソッドが戻らない場合、システムはプロセスを完全に終了する可能性があります。

(中略)

このメソッドを呼び出した後、アプリは通知を投稿して、関心のあるオブジェクトに遷移に応答する機会を与えます。

https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623111-applicationwillterminate

また、applicationWillTerminate が呼ばれた後、 willTerminateNotification の通知を発行するとも書かれています。

これらから、アプリ終了前のクリーンアップ処理は以下に書くのが良さそうです。

  • AppDelegate の「applicationWillTerminate」内
  • 「willTerminateNotification」の通知受信時

基本は AppDelegate.applicationWillTerminate に処理を書き、それ以外の場合(ライブラリなど直接AppDelegateのメソッドを使えない場合)は「willTerminateNotification」の通知を購読しておいて処理を書くのが良いと思われます。

アプリの存続時間

ユーザーがアプリスウィッチャーからアプリをスワイプして、バックグラウンドからアプリを削除する操作を行ってから、完全にアプリのタスクがキルされるまではどの程度猶予があるでしょうか。

AppDelegate.applicationWillTerminate の場合、公式ページに5秒程度は猶予があるとのことなので、実際に確かめてみました。

class AppDelegate: UIResponder, UIApplicationDelegate {
    (省略)
    
    func applicationWillTerminate(_ application: UIApplication) {
        print("applicationWillTerminate start")
        // 5秒間スリープ
        sleep(5)
        print("applicationWillTerminate end")
    }
}

出力は以下の通りです。

applicationWillTerminate start
applicationWillTerminate end

-> 5秒間スリープした後のprint文が出力されています。

willTerminateNotification を受け取ってから処理できる時間

こちらは公式ページに詳細が記載されていませんでしたので、実際に確認してみました。

class ViewController: UIViewController, CLLocationManagerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // willTerminateNotification の通知を購読
        NotificationCenter.default.addObserver(self, selector: #selector(willTerminate(_:)), name: UIApplication.willTerminateNotification, object: nil)        
    }

    /// アプリキル時の通知(willTerminateNotification)を受け取った時に呼ばれる
    /// https://developer.apple.com/documentation/uikit/uiapplication/1623061-willterminatenotification
    @objc func willTerminate(_ notification: Notification) {
        print("willTerminateNotification start")
        // 5秒間スリープ
        sleep(5)
        print("willTerminateNotification end")
    }

出力は以下の通りです。

applicationWillTerminate start
applicationWillTerminate end
willTerminateNotification start
willTerminateNotification end

-> UIApplication.willTerminateNotification の通知を受け取ってから5秒間スリープしても、print 文が出力されていました。こちらで処理できる時間も AppDelegate.applicationWillTerminate とほぼ同じと考えて良さそうです。

アプリキル操作後、5秒以上存続できるかどうか

class AppDelegate: UIResponder, UIApplicationDelegate {
    (省略)
    
    func applicationWillTerminate(_ application: UIApplication) {
        print("applicationWillTerminate start")
        // 6秒間スリープ
        sleep(6)
        print("applicationWillTerminate end")
    }
}

デバッグエリアの出力は以下の通りです。

applicationWillTerminate start
Message from debugger: Terminated due to signal 9

-> スリープ後の処理が完了していません。(強制終了している)

このため、クリーンアップ処理は公式に記載の通り5秒程度で終わらせる必要がありそうですね。