修正日: 07/07/07
練習問題6
解答編
では解答。ここまでの知識に基づいての模範解答はおそらく「NSWindowのサブクラスを作る」です。「Become An Xcoder」日本語版の48〜49ページを読んでみてください。
つまりNSWindowのサブクラスを作り、「閉じる」メソッドを自作(オーバーライド)して、そこにアプリケーションを終了させるコードを追加すれば良いのです。
と、ここまでが模範解答。しかしCocoaではいちいちサブクラスを作らなくてももっと簡単な方法があります。それを以下に説明します。
まずは終了させるためのソースコードを紹介します。まずはbmiWindowへのアウトレットを追加します。今まではInterface Builderでクラスを作る時にアウトレットを設定しておいて、ファイルを作る時に自動的に生成されていましたが、後から追加したい時には以下のようにします。
まず、「BMIController.h」にウィンドウへのアウトレットの定義を一行追加して保存します。
@interface BMIController : NSObject
{
IBOutlet id bmiWindow; //この行を追加
IBOutlet id bmiField;
この行を追加しただけではInterface Builderの方にアウトレットは増えません。次にXcodeのBMIController.hファイルをInterface BuilderのMainMenu.nibウィンドウにドラッグします。
これでようやくBMIControllerにbmiWindowへのアウトレットが追加されますので、ウィンドウにつないでください。
次に以下のメソッドをBMIControllerクラスに追加してあげてください。サンプルはこちら。
- (void)awakeFromNib
{
NSNotificationCenter *aCenter=[NSNotificationCenter defaultCenter];
[aCenter addObserver:NSApp
selector: @selector(terminate:)
name: @"NSWindowWillCloseNotification"
object: bmiWindow];
}
一行一行(といっても実質三行しかありませんが)見ていきましょう。
最初の行、メソッドの名前ですが、「awakeFromNib」です。この名前を覚えていますか?忘れていたら「Become An Xcoder」日本語版64ページ以降を読み直してください。・・・そうです、アプリケーションが起動した時に、自動的に実行されるコードです。
メソッド内の一行目では「NSNotificationCenter」というクラスのオブジェクトを作っています。とりあえず細かいことは抜きにして、「NSNotificationCenterというものを準備している」とだけ理解してください。それが何者かということについては後で説明します。
メソッドの二行目(四行に分かれていますが実際は一つの長いメソッドの呼び出しです)では、今作った「NSNotificationCenter」に何かメッセージを送っています。日本語に翻訳するとこういう意味です。
「aCenter」にお願いなんだけど、「bmiWindow」から、「NSWindowWillCloseNotification」という連絡があったら、「NSApp」に、「terminate:」というメッセージを送ってください。
この「NSWindowWillCloseNotification」というのは読んで字のごとく、「これからウィンドウを閉じますよ」というメッセージ、「NSApp」はCocoaアプリケーション本体のオブジェクト、「terminate:」というのはアプリを終了するメソッドです。つまりもう少し意訳すると、
「aCenter」にお願いなんだけど、「bmiWindow」から、「これからウィンドウを閉じる」という連絡があったら、「NSApp」に、「アプリを終了しろ」というメッセージを送ってください。
つまりこのNSNotificationCenterというのは、Cocoaアプリの中の伝言センターみたいな役割を果たしていて、アプリが起動したらすぐに「ウィンドウが閉じる」→「終了の連絡」を登録しているわけです。図で書いてみるとこんな感じ。登場人物はBMIController、bmiWindow、NSAppそしてNSNotificationCenterの四人です。
まずawakeFromNibで
ウィンドウのクローズボタンを押すと
そして
という手順です。ちなみにNSAppはアプリを終了する前にそのことをNSNotificationCenterに報告します。
その報告を受けるとNSNotificationCenterは、事前に登録してあった各オブジェクトに「これからアプリが終了するぞ〜」と連絡して回るわけです。
ということでここで再び問題。アプリケーション終了の際になにか(例えばデータの保存など)をしたい場合はどのようにしたら良いでしょうか?これもコードを書くのはさすがに難しいと思うので、どのような手順で実装すれば良いかだけ考えてみてください。
・・・
・・・
頭の整理がつきましたか?答えはこうです。
「NSApp」が「アプリケーションを終了する」という連絡があったら、「BMIController」にメッセージを送るように「NSNotificationCenter」に登録する
では参考にコードを見てみましょう。(NSWindowWillCloseNotificationの登録は今回は省略してあります)
- (void)awakeFromNib
{
NSNotificationCenter *aCenter=[NSNotificationCenter defaultCenter];
[aCenter addObserver: self
selector: @selector(applicationWillTerminate:)
name: @"NSApplicationWillTerminateNotification"
object: NSApp]; //a
}
最初の例と同様にまずNSNotificationCenterを準備します。そして自分をそこに登録します。登録の一番下の行aから、下から順番に見ていきましょう。
まず「object:」で「誰(どのオブジェクト)からの連絡」を指定します。NSAppです。次の「name:」は「どんな連絡」で、この場合は「NSApplicationWillTerminateNotification」です。これは「アプリケーションをこれから終了する」という連絡です。そして一行飛ばして最初の行、「addObserver:」で「誰にメッセージを送るか」を指定します。ここではself、つまりBMIControllerにメッセージを送信するように指定します。そして最後に上から二行目「selector:」でどんなメッセージを送るか、つまりどのメソッドを実行するかを指定します。ここでは「applicationWillTerminate:」を指定します。ちなみに「@selector()」っていうのは何かというと、メソッドを指定する書き方です。「@"something"」と書くと「something」という文字列になりましたが、「@selector(something)」と書くと「something」というメソッドの名前になります。
ややこしくなってきたのでひとまずまとめ。これでどういうことが起きるかというと、
「NSApp」がアプリを終了しようとして「NSApplicationWillTerminateNotification」を「NSNotificationCenter」に送ると、「NSNotificationCenter」は「BMIController」の「applicationWillTerminate:」というメソッドを実行
するということです。とすると、当然「applicationWillTerminate:」というメソッドをBMIControllerに実装しなくてはいけません。例えばこんな感じ。
- (void)applicationWillTerminate:(NSNotification *)pNotification
{
NSLog(@"Haaku sita!");
}
アプリケーションが終了する寸前でウィンドウに何かを表示しても見えないので、NSLogで文字を出力しています。Xcodeの「実行ログ」ウィンドウに表示されますので確認してみてください。最終的なサンプルはこちら。
と、今回もややこしくなりましたがいかがでしょうか?今回はNSNotificationCenterを使いましたが、これをどうやって使うか、という事はまだ覚えなくても良いと思います。ここで理解してもらいたいのは、「オブジェクト指向のプログラムはどうやって動いているか」という事です。「Become An Xcoder」の中でも「ボタンやウィンドウがオブジェクトで・・・」と説明していますが、この例の方が「プログラムはオブジェクト同士が連絡し合って動いている」というのがよりはっきりつかめるのではないかと思います。このようにいろいろな機能を持ったオブジェクト、特に出来合いのオブジェクトをうまく組み合わせるのが、オブジェクト指向プログラミングのコツです。もちろんそのためには、どのクラスがどのような機能を持っているのかを良く勉強しないといけませんけどね。
次回は、テキストフィールドだけで味気ないウィンドウをもう少し見栄えよく(?)変更してみます。