修正日: 09/03/10
練習問題15
解答編
バグのないアプリを作ることができましたか?私がCalcBMIを作った時も一回はまりました。ぱっと考えると以下のような感じのコードになると思います。
- (void)setHeight:(float)pHeight
{
height=pHeight;
[self calcurateBMI];
}
- (void)setWeight:(float)pWeight
{
weight=pWeight;
[self calcurateBMI];
}
- (void)setBmi:(float)pBmi
{
bmi=pBmi;
[self calcurateWeight];
}
- (void)calcurateBMI
{
float aBmi=weight/(height/100)/(height/100);
[self setValue:[NSNumber numberWithFloat:aBmi] forKey:@"bmi"];
}
- (void)calcurateWeight
{
float aWeight=bmi*(height/100)*(height/100);
[self setValue:[NSNumber numberWithFloat:aWeight] forKey:@"weight"];
}
しかしこれではアウトです。例えば体重を変化させたとき体重の数値が変わって「calcurateBMI」メソッドが呼び出されます。これは良いのですが、「calcurateBMI」メソッド内でbmiの値が変わり、「setBmi:」のセッタが呼び出されます。「setBmi:」は「calcurateWeight」を呼び出します。「calcurateWeight」は体重をセットします。体重が変化したので「calcurateBMI」が呼び出されて・・・無限ループです。すぐにクラッシュしてしまいます。対策はどうすれば良いでしょうか?「calcurateBMI」「calcurateWeight」メソッド内で「setValue:forKey:」を使うのをやめる?それでは体重を変化させてもウィンドウ上のBMIの数値が変化してくれません。よく考えてみてください。
一つの解決策です。セッタを以下のように書き換えます。
- (void)setHeight:(float)pHeight
{
if(height!=pHeight)
{
height=pHeight;
[self calcurateBMI];
}
}
- (void)setWeight:(float)pWeight
{
if(weight!=pWeight)
{
weight=pWeight;
[self calcurateBMI];
}
}
- (void)setBmi:(float)pBmi
{
if(bmi!=pBmi)
{
bmi=pBmi;
[self calcurateWeight];
}
}
どういう事でしょうか?見ての通り、「数値が変化していない時は再計算をしない」ように改造してあります。例えば体重を変化させたときBMIを計算してセットします。BMIがセットされたら今度は体重を計算してセットしようとするのですが、体重を元に計算されたBMIなのでそれを元に計算しても元の体重に戻るだけです。なので体重の数値が同じであればここで再計算はやめます。これで無限ループを断ち切ることができます。
一つの問題は整数値の計算ではなく小数の出る計算なので、正確に同じ数値に戻らないかもしれない、という点ですが、よくはわかりませんがとりあえずこれで動きます。プログラムにもっと詳しい人なら偶然なのか必然なのかわかると思いますが、不幸にして私には判断できません。本当はそんな書き方をしてはいけないのですけどね。他にも無限ループを回避する方法があると思いますので考えてみてください。
配布版のCalcBMIはこれとはまた別の方法で問題を回避しています。次回はその方法について勉強します。