applicationShouldTerminate(_:) で丁寧に終了しよう
- Apple
- ソフトウェア
- 開発
Mac アプリケーションを開発するなら、数ある Delegate の中でも NSApplicationDelegate
は無視できない存在だ。アプリケーションの起動や終了、ファイルを開くときなど、ことあるたびにこの Delegate のメソッドが呼び出され、その先の挙動をカスタマイズすることができる。
最近では SwiftUI を使って開発することもあるが、たとえ SwiftUI の App
に Scene
を並べる「フル SwiftUI」と呼べるような使い方であっても痒いところが出てきたらこれが重宝する。
この使い方は何もハック的に AppKit 側の世界に手を伸ばさずとも @NSApplicationDelegateAdaptor
という標準の API でカバーされている。使い方は簡単で、通常通りに Delegate のクラスを定義したらこれをつけて App
内にプロパティとして宣言するだけだ。
終了を延期したい
ここからが本題。最近いくつか Mac アプリケーションを作っていて終了前に少し長めの処理をさせたいことがでてきた。たとえば録音の停止。録音中にそのまま終了すると再生できないファイルが生成されてしまうので後処理が必要だ。
「終了前に処理」と考えると単純に applicationWillTerminate(_:)
が思い浮かぶ。確かにそのためのメソッドだがこれは呼ばれたあとすぐにそのまま終了に突き進んでしまう。
秒単位で時間がかかるのであればユーザに見えるよう UI 上に進捗状況を表示したいだろうし、それどころかその処理が失敗したらユーザにエラーを見せて終了をキャンセルしないといけないなど、この単純なコールバックだけでは不十分だ。
AppKit にはそのための機能があり、NSApplicationDelegate
にメソッドが用意されている。
will ではなく should なのがポイント。しかも返り値は Bool
ではなく特別な型だ。これはいったい何なのだろう。
ユーザが command + Q を押すと NSApplication.terminate(_:)
が呼ばれる。それだけでは終了が決定したことを意味せず、Delegate の applicationShouldTerminate(_:)
が呼ばれ、その返り値に応じて挙動が変わるのだ。
その返り値となるのが次の型。
terminateCancel
:終了をキャンセルterminateNow
:このまま終了terminateLater
:あとで終了
というように、名前から想像できるままの意味だ。だけど、最後の terminateLater
は使い方を想像しにくい。これを返したらどのタイミングで終了するの?
鐘を鳴らすのはあなたである。終了の可否が決まったらあなた自身が NSApplication
に対して次のメソッドを呼ぶ必要がある。
shouldTerminate
に true
を渡せばそのまま終了するのはもちろんのこと、false
を渡せば終了をキャンセルできる。そこでエラーを表示してもいいだろう。
ここまでをまとめると、使い方の雰囲気は次のようになる。Task
を使えば await
が入っても大丈夫だ。もちろん処理が終わったらどんな場合でも reply を呼び忘れないように注意する。
UI 上の注意点としては、この reply 待ちの状態でもユーザが UI を操作できてしまうことだ。アプリケーションが終了するはずのタイミングでユーザが新しいことを始めないように、何らかの方法でウインドウへの操作をブロックしたほうがいいだろう。
この API を使ったわかりやすい例は「保存しますか?」のダイアログ。ユーザがメニューから終了を選んでもダイアログが出てるかぎりは終了しないし、キャンセルボタンを押せば終了そのものがキャンセルされる。そしてシートとしてダイアログが出ているからウインドウの UI に対する操作はきちんとブロックされているのだ。
Apple の細かい工夫
この API はよく見ると細かい作り込みがあって、command + Q を押したあとにアプリケーションメニューのハイライトがそのまま残ってくれるのだ。ちょっとした工夫だけど「終了の途中です」感がよく出てて好き。
Share
リンクも共有もお気軽に。記事を書くモチベーションの向上に役立てます。