補足資料A
補足資料A もしくはSupplement A
NSApplicationのデリゲート
1. NSApplicationのデリゲート
Macのアプリケーションの場合、最後のメインウィンドウを閉じてもそのアプリケーションは終了しないのがデフォルトの仕様になっています。しかし単一ウィンドウのアプリケーションではこの仕様は不便な時もあります。そこでこのような場合は第8章でも説明したようにメインウィンドウを閉じた時点でアプリケーションも終了するように変更したり、あるいはウィンドウ自体をクローズできないように変更したりします。
そしてアプリケーションが終了する時に未保存データが残っている場合はウィンドウを閉じる時に送信されるデリゲートメソッドにデータを保存するコードを追加して対応しました。この方法は「棚からぼた餅」的に意外なほどうまく単一ウィンドウのアプリケーションにマッチします。ウィンドウをクローズする時には当然前述のデリゲートメソッドが送信されて保存が行われることになります。またアプリケーションを終了する時にもまずウィンドウのクローズが行われるので同じくウィンドウのクローズに関するデリゲートメソッドが送信されて保存が行われます。
またウィンドウのクローズ機能を無効にした場合には、確かにユーザが自らウィンドウを閉じることはできませんが、アプリケーションの終了を選ぶと、前述にようにアプリケーションはまず開いているウィンドウを閉じようとします。その結果ウィンドウをクローズする時のデリゲートメソッドが送信されて未保存のデータの保存が行われます。
以上のように結果オーライでこれはこれで良いとは思います。しかしウィンドウのクローズ機能を無効にしたのであれば、未保存データの保存などをウィンドウのクローズのデリゲートメソッドに頼るべきではないでしょう。ではどこにデータを保存するコードを記述すれば良いのでしょうか。ここはそのものズバリ、NSApplicationというアプリケーションの起動と終了を行っているオブジェクトのデリゲートメソッド(委任メソッドとも呼びます)にその名のとおりに保存コードの実行を委ねるのが正当な方法でしょう。この補足資料BではこのNSApplicationのデリゲートメソッドを利用する方法を紹介していきます。
2. 作業準備
8章2節で作成したプロジェクトをフォルダごと複製してください。私のほうでは「MyClip 8.2」という名前にしているフォルダです。そして複製したほうを「MyClip
InvalidCloseButton」あるいは「MyClip
Supplement A」もしくはそのものずばりで「MyClip補足資料A」などのフフォルダ名に変更してください。私の場合は「Supplement A」というフォルダ名にしました。フォルダ名の変更が終わりましたらこの新しく作ったフォルダの中のMyClip.xcodeprojを起動してください。そして続けてMainMenu.xibを起動します。
Interface Buiderが起動してMainMenu.xibが開きましたらMyClipウィンドウを選択してWindow Attributes インスペクタパネルを表示します。インスペクタパネルのControlsグループからCloseのチェックを外します。
図:Window Attributes Close
また、8章でMyClipの動作テストをしていた時に気になったのですがテキストビューにUndoがないのは不便です。今度はMyClipウィンドウ内のText Viewを選択してください。選択ができたかどうかは必ずインスペクタパネルのタイトルで確認するようにしてください。Text View Attributesインスペクタパネルが表示したらAllowsグループでUndoにチェックを入れます。この項目はチェックがされていないほうがデフォルトの設定だったと思います。おそらくメモリ占有量を減らすためにデフォルトでは外されているのだと思われます。
図Text View Attributes Undo
Interface Buiderでの作業はこれで一旦終わります。ファイルを保存してXcodeに戻ってください。プロジェクトウィンドウの「ビルドして進行」をクリックします。当然のことながらUndoが有効になったこと以外は8章のコラムでのテストの時とまったく同じ動作をするはずです。なおRedo(やり直す)もメニューからでも、command + shift + Zショートカットキーからでも行うことができるように変わっています。
3. メインウィンドウのdelegate解除とNSApplicationのdelegate接続
現在は、未保存データがあった場合はメインウィンドウのデリゲートメソッドで処理するようになっています。まずこのメインウィンドウのdelegateの接続を解除して、代わりにNSApplicationのdelegateの接続先としてControllerを登録します。
MainMenu.xibファイルを再び開いてください。interface Builderが起動しましたらMainMenu.xibウィンドウでWindow(MyClip)を右クリックします。接続一覧が表示されましたらdelegateの行の×印をクリックしてdelegateの接続を解除します。
図Window - delegete 接続解除
次はNSApplicationのdelegateの接続ですが、MainMenu.xibウィンドウの中にはApplicationというproxy object(代理オブジェクトとも呼びます。詳しくはのちほど説明いたします)が存在しています。しかしMyClipのようなシングルトンアプリケーションの場合にはApplication(正確にはNSApplication)がこのxibファイルのオーナー(持ち主)になっています。MainMenu.xibウィンドウの中のFile's Ownerというオブジェクトはこのxibファイルのオーナーを表す代理オブジェクトです。一応確認してみましょう。MainMenu.xibウィンドウでFile's Ownerを選択してインペクタパネルでIdentityタブを選びます。Classという項目がNSApplicationになっていることが確認できます。というかFile's Ownerを選んだ時点でインスペクタパネルのタイトルがすでにApplication
○○になっていたことだろうと思います。
□Xcode 3.1の場合
図Application Identity Xcode 3.1
□Xcode 3.2の場合
図Application Identity Xcode 3.2
DRAFT
このようにFile's OwnerとApplicationがともにNSApplicationを表している場合にはどちらを接続元にしても結果は同じことになります。しかしこのような場合には一応File's Ownerから接続するという慣習になっています。では接続を行います。MainMenu.xibウィンドウのFile's OwnerからControllerへ接続線を伸ばします。
図Application delegate接続1
接続先(Outlets)からdelegateをクリックして接続を完了させます。
図Application delegate接続2
これでアプリケーションの外枠は完成しました。あとはXcodeでコーディングしていくだけです。
4. 代理オブジェクト
MainMenu.xibウィンドウを見てください。ウィンドウの中で上段の3つ、File's Owner、First Responder、ApplicationはProxy(代理)オブジェクトと呼ばれています。この3つは実際にはxibファイルに含まれているオブジェクトではありません。しかし今回の場合のようにdelegateの接続などをする場合のことを考えて便宜的にMainMenu.xibウィンドウに表示されています。
File's Ownerはこのxibファイルのオーナーを表しています。xibファイルのオーナーですからこのことからも本来xibファイルに含まれているものではないことが分かります。このファイルズオーナーがどのクラスなのかを調べるにはFile's Owner代理オブジェクトを選択してインスペクタパネルの右から2番目の「Identity」タブを選びます。すると一番上の「Class」という欄にオーナーのクラスが現れます。MyClipの場合はNSApplicationになっています。というかシングルトンアプリケーションの場合はFile's OwnerはNSApplicationになります。
このFile's Ownerがどのクラスになっているのか調べる方法は先ほども説明いたしました。念のためにもう一度説明を行っているだけです。
図Application Identity インスペクタパネル
なお、First Responderはその時々にユーザイベントを受け取るオブジェクトを表しています。またNSApplicationの代理オブジェクト「Application」もMainMenu.xibウィンドウには表示されています。当然このApplicatio代理オブジェクトからデリゲート接続を行うこともできます。しかし今回の場合のようにFile's OwnerがNSApplicationである場合にはFile's Ownerからデリゲートに接続を行うのが一般的です。なおどちらの代理オブジェクトから接続を行っても当然、結果は同じになります。
以上でInterface Builderでの作業は終了しました。作業を保存してXcodeに戻ります。
5. NSApplicationのデリゲートメッド
NSApplication Class Refereneceを開いてスクロールしていくとTerminating Applicationsというグループが見つかります。この中にはMyClipを終了させるためにすでに使っているterminate:メソッドなども見受けられます。そして次の3つのデリゲートメソッドが書かれています。
- applicationShouldTerminate:
-
applicationShouldTerminateAfterLastWindowClosed:
- applicaationWillTerminate:
今回は最後のapplicationWillTerminate:デリゲートメソッドを使うことになることはすでにお分かりいただけるだろうと思います。
図NSApplication Class Reference - Terminating Applications
Terminating Applicationsグループの前にLaunching Applicationsグループがあります。それぞれアプリケーションの「終了」とアプリケーションの「起動」という意味でterminateとlaunchを使っています。
- applicationWillTerminate:メソッドをクリックして詳細を見てみます。シグネチャは次のとおりになっています。
- (void)applicationWillTerminate:(NSNotification
*)aNotification
これで必要な情報は集まりました。コーディングをはじめましょう。
6. コーディング
コーディングの対象となるのはController.mだけになります。Modelクラスに変更はありません。また、Controller.hの中にデリゲートメソッドを新たに宣言しなおす必要はありません。
※逆に宣言しても問題は起こりません。
Controller.m
次のデリゲートメソッドをシグネチャだけではなく実装も含めてすべて削除します。
- (void)windowWillClose:(NSNotification *)notification
そしてコードファイルの同じ場所に次の新しいデリゲートメソッドを定義実装まで含めて記述します。
- (void)applicationWillTerminate:(NSNotification *)aNotification
なお「同じ場所に記述してください」と言いましたがメソッドを記述する順番に決まりはありません。そのためにヘッダファイルにメソッド宣言をしているわけですから。あくまでも見やすいか読みやすいかという基準だけでメソッドの記述順を決めています。
変更するデリゲートメソッドの変更前と変更後のすべてのコードは次のようになります。
変更前
- (void)windowWillClose:(NSNotification *)notification
{
if ([window isDocumentEdited])
[self writeToFile:self];
[NSApp terminate:window];
}
変更後
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
if ([window isDocumentEdited])
[self writeToFile:self];
}
変更後メソッドには [NSApp terminate:window];
というメッセージ式はありません。変更後のデリゲートメソッドはアプリケーションの終了が選ばれた時に実行される(送信される)メソッドです。したがってここで再度アプリケーションの終了を指示する必要はありません。
●実行
では作業を保存してコンパイルしてみましょう。
メインウィンドウが起動しましたら色々と試してみてください。
クローズボタンは使えなくなっています。アプリケーション終了時に未保存のデータがあれば強制的に保存されるようになっています。
お疲れ様でした。これで補足資料Aを終わります。
ここで行っているようなアプリケーション終了時に未保存のデータを強制的に保存する振る舞い(動き)はMacのアプリケーションとしては標準的な動きではありません。アプリケーション終了時に未保存のデータが残っている場合には警告のダイアログシートを表示して保存するかどうかをユーザに決めてもらうというMac標準の振る舞いにしたほうが良いでしょう。
This site is available in Safari and Snow Leopard. | (c) viva Cocoa 2006 - 2010 |