修正日: 07/07/07
練習問題7
解答編その二 コードの修正
ではいよいよお待ちかね、ソースコードの修正です。プログラムの基本方針ですが、テキストフィールドとスライダー計六個へのアウトレットを準備して、前と同じようにアクションをつないで計算を実行します。
@interface BMIController : NSObject
{
IBOutlet id bmiWindow;
IBOutlet id bmiField;
IBOutlet id bmiSlider;
IBOutlet id heightField;
IBOutlet id heightSlider;
IBOutlet id weightField;
IBOutlet id weightSlider;
}
そうしたらあとはInterfaceBuilderを使って、heightField、heightSlider、weightField、weightSliderを既に作ってあるcalcBMI:メソッドに、bmiField、bmiSliderをcalcWeight:メソッドに接続して一丁上がり・・・と言いたいところですが、残念ながらそう簡単にはいきません。これではテキストフィールドをいくら変えてもスライダーの値がまったく変わりません。じゃあ
[bmiSlider setFloatValue:[bmiField floatValue]];
という風にテキストフィールドの値をスライダーにセットしてあげれば良いか、というと、これではスライダーをいくら動かしてもテキストフィールドは変わりません。スライダーが変更されたらスライダーの値をテキストフィールドにセット、テキストフィールドが変更されたらテキストフィールドの値をスライダーにセットしたいのですが、今のメソッドでは変更されたのがいったいどっちなのかがわかりません。
ではどうやって解決するか。まず考えられるのが六個のアウトレットそれぞれに別々のアクションを準備してやる、という方法です。しかしこれは面倒くさくてあまりおすすめできません。この問題はヒントで紹介した「sender」を活用することでもう少しスマートに解決することができます。
ヒントで説明しましたが「sender」というのはアクションを送信したオブジェクトです。senderの値こそがユーザーの設定した値である、と考えられるので、この値をそれぞれのオブジェクトにコピーして、これを使ってBMIを計算してやれば間違いありません。ただしこの値が身長なのか体重なのかまではわからないので、(どっちかわからないけど)身長が変更された、体重が変更された、BMIが変更された、という三つのアクションを準備してやる必要があります。ということでコードはこんな感じになります。
- (IBAction)changeBMI:(id)sender
{
[bmiField setFloatValue:[sender floatValue]];
[bmiSlider setFloatValue:[sender floatValue]];
}
- (IBAction)changeHeight:(id)sender
{
[heightField setFloatValue:[sender floatValue]];
[heightSlider setFloatValue:[sender floatValue]];
}
- (IBAction)changeWeight:(id)sender
{
[weightField setFloatValue:[sender floatValue]];
[weightSlider setFloatValue:[sender floatValue]];
}
senderはどっちだかわからないのでとりあえず両方にセットしてあげます。テキストフィールドとスライダーのどちらかはsenderと同じなので、また値をセットするのは無駄といえば無駄なのですが、今のMacは十分に速いので気にする必要はありません。いちいち気にしてセットしないようにするコードを書く方が時間の無駄です。
これでは値が変わるだけで計算していないので、計算メソッドを作り、上記changeXXX:メソッドから呼び出してやります。以下サンプル。
- (void)calcWeightAndSet
{
float anWeight;
anWeight=calculateWeight([heightField floatValue], [bmiField floatValue]);
[weightField setFloatValue:anWeight];
[weightSlider setFloatValue:anWeight];
}
- (void)calcBMIAndSet
{
float aBMI;
aBMI=calculateBMI([heightField floatValue], [weightField floatValue]);
[bmiField setFloatValue:aBMI];
[bmiSlider setFloatValue:aBMI];
}
あとはヘッダ(BMIController.h)を正しく書き直して、nibファイルでアクションを正しく接続してやれば完成です。そこら辺は今までの知識でできるはずなので、ちゃんと理解しているか確認してみてください。そろそろいろいろバグも出てくると思いますが、根気よく原因を探して直してください。完成サンプルはこちら。
そろそろいっぱしのアプリケーションらしくなってきたと思いますがいかがでしょうか?
さて今回のポイントはさらっと何の説明も無しに使いましたが、
[sender floatValue]
という部分です。senderはid型で、idとは「何かオブジェクトへのポインタ」であるということはヒントの中で説明しました。この「何だかわからない何か」にメッセージを送って正しく動作するんでしょうか?これがオブジェクト指向プログラミングの特徴の一つ、「ポリモーフィズム」(多態性)であり、Objective-Cの特徴である「動的結合」です。
ポリモーフィズムについては「Become An Xcoder」の62ページでちょこっと説明してありますので読んでみてください。このように同じメソッド名を持つことで、相手が誰なのかを気にすることなくメッセージを送ることができます。floatValueというメソッドで何をすれば良いかは相手のオブジェクトが知っているので、プログラマは何も気にする必要は無いのです。たとえばスライダーよりもっと格好良い自作のコントロールを作った場合でも、floatValueというメソッドを実装しておけばNSSliderと同じようにウィンドウに配置して扱ってやることができます。
二番目のキーワードの動的結合ですが、簡単に言えばプログラム実行時にどの型のどのメソッドが実行されるか決まる、ということです。Javaのような静的結合の言語ではコンパイル時に型がチェックされ、どのメソッドが実行されるかが決定されます。それに対し、動的結合ではコンパイル時の型チェックは形式的なもので、実際メソッドが実行できるかどうか、どのメソッドが実行されるかはプログラム実行中に動的に決定されます。この特徴をうまく使うことでプログラム実行中にオブジェクトを入れ替えたり、いろいろな種類のデータを一元的に管理できたり、非常に自由度の高いプログラムを作ることができます。
まあここら辺は頭で理解していなくても、実際いろいろ作っているうちに慣れてくると思います。とりあえず「そういうもんだ」という程度だけ覚えておいてください。
次回も引き続きCalcBMIをいじります。次回は「インスタンス変数」を使ってコードを少し整理します。