「NSInvocationをカテゴリで拡張した」の修正
以前の、「NSInvocationをカテゴリで拡張した」の修正。
詳解Objective-C 2.0 第三版 P537より、KVCで自動変換出来る構造体は、
自動的に受け渡し出来るのはCocoaで標準的に使われている四種類の構造体 NSPoint, NSRange, NSRect, NSSizeに限られています。
との事なので、NSPoint, NSRange, NSRect, NSSizeの四つの構造体をサポートするように変更する。
if文の固まりの末尾に、以下を追加した。
else if( 0 == strcmp(theReturnType, @encode(NSPoint)) )
{
theResult = [NSValue valueWithPoint:*(NSPoint*)thePtr];
}
else if( 0 == strcmp(theReturnType, @encode(NSSize)) )
{
theResult = [NSValue valueWithSize:*(NSSize*)thePtr];
}
else if( 0 == strcmp(theReturnType, @encode(NSRect)) )
{
theResult = [NSValue valueWithRect:*(NSRect*)thePtr];
}
else if( 0 == strcmp(theReturnType, @encode(NSRange)) )
{
theResult = [NSValue valueWithRange:*(NSRange*)thePtr];
}
else
{
theResult = [NSValue valueWithBytes:thePtr
objCType:theReturnType];
}
以上のサポートでKVCで標準でサポートしている型の自動変換をサポートしたハズ。
メソッド名合成を使ったプログラミング(1)
Objective-Cでは文字列型NSStringとセレクタ型SELを相互に変換出来る。この変換機能で実行時に生成した文字列からメソッドを呼び出す事が可能になる。
スクリプト言語では当たり前の機能だが、コンパイル言語でこのような機能があるのは少ない。
ここで、文字列からメソッド名を合成する事、その合成したメソッドを積極的に使ったコーディングをそれぞれ、「メソッド名合成」及び「メソッド名合成プログラミング」と命名する事にする。
メソッド名合成
文字列型NSStringとセレクタ型SELの相互変換関数として、以下の2つがある。
NSString *NSStringFromSelector(SEL aSelector);
SEL NSSelectorFromString(NSString *aSelectorName);
文字列からSEL型を作るのはもちろん、SEL型も文字列型に変換後は編集可能になる。編集した文字列型を再びSEL型に戻せば、SEL型の編集も可能と見なせる。
単純な例として、プロパティ名からsetterメソッドを合成する関数を示す。
SEL setterSelectorFromName(NSString* inName)
{
NSString* theResultString;
theResultString = [NSString stringWithFormat:@"set%@:",
[inName stringByReplacingCharactersInRange:NSMakeRange(0,1)
withString:[[inName substringToIndex:1]
uppercaseString]]];
return NSSelectorFromString(theResultString);
}
この関数は、@”test”を渡すと@selector(setTest:)を返す動作をする。
合成したメソッドの呼び出し
プロトコルや非公式プロトコルのメソッドと同じように、合成したメソッドは対象となるオブジェクトにメソッドの実装があるとは保証されていない。
よって、セレクタが呼び出し可能かを確認後呼び出しをしなければならない。
コードとしては、以下のようになる。
if( [self respondsToSelector:theSelector] )
{
[self performSelector:theSelector
withObject:theObject];
}
これはデリゲートやプロトコルを使ったプログラミングと同じようになる。
すこしだけ異なるのは、プロトコルと違いメソッド名は動的に決まるので、[NSObject performSelector:]やNSInvocationを使ったメソッド呼び出しを行う事になる。
メソッド名合成プログラミング
メソッド名合成プログラミングの応用例がCocoaのフレームワークの中にある。 ここでは、判り易い例としてkey値コーディングのインディックス付きアクセッサを示す。
インディックス付きアクセッサは以下の2つのメソッドをサポートする必要がある。
- (NSInteger) countOf_KEY_;
- (id) objectIn_KEY_AtIndex:(NSInteger)index;
ここで、”_KEY_”はkey値コーディングでいうキー文字列が入る。
この2つのメソッドを定義すると、そのオブジェクトはkey値コーディング上ではプロパティ_KEY_を持っているように振る舞う事が出来る。
プロトコルと異なり、_KEY_は任意の文字列に出来る。実行時に対で実装されていれば実行出来る。
同じような例として、プロパティ値の検証用メソッドがある。
- (BOOL) validate_KEY_:(id*)ioValue
error:(NSError**)outError;
ここでも、”_KEY_”はkey値コーディングでいうキー文字列が入る。
これらの例のように、Cocoaでは「メソッド名合成を使ったプログラミング」は既に使われている。
つづく。
validateuserinterfaceitem(8)
NSUserInterfaceValidationsプロトコルは、CocoaのUIの状態を更新する仕組みを提供するプロトコルだ。
UIにはNSToolbar、NSMenu、NSView、NSControlのサブクラス群が含まれる。
メニューのenable/disableやチェックマークを付ける等の状態を更新する、OpenStep時代のNSMenuActionResponder非公式プロトコルを発展させた物だと思われる。
NSUserInterfaceValidationsプロトコルは、NSToolbar用のNSToolbarItemValidationプロトコルや、NSMenu用のNSMenuValidationプロトコルを統一的に扱う為の物なのだろう。
ここで問題なのは、NSToolbarやNSMenuにはautovalidates等のプロパティがあり、自動で更新処理を行う事が出来る。
しかし、NSViewやNSControlには自動で更新を行う仕組みは無い。
当初、私は個々のNSControlのサブクラスで、”NSWindowDidUpdateNotification”をとらえて更新させるコードを書いていた。
この方式の欠点はNibエディター上でUIを配置する毎に、Class名を書き換える必要があり煩雑である事だった。
この欠点を解消する為に、Notificationを個々のcontrolでとらえるのではなく、NSWindowのupdateメソッドをオーバーライドして、個々のcontrolの状態を更新する方法に変更した。
NSWindowとNSPanelのサブクラスをそれぞれ作る事で実現している。
コードは以下の通り、
NSWindowのupdateメソッドについてのメモ
- NSWindowのupdateメソッドは、NSWindowDidUpdateNotificationnを通知センターにポストする以外の処理は行わない。
- NSApplicationのupdateWindowsメソッドを呼び出す事で、アプリケーション内の全てのNSWindowに対してupdateメソッドを呼び出す事が出来る。
- イベントループが一回転するたびに、NSWindowのupdateメソッドは呼ばれる。
- updateメソッドが呼ばれるタイミングはNSWindowが再描画される前。
- NSWindowのサブクラスでは、updateメソッドをオーバーライドして、モデルデータの内容を各種UIに反映する事が出来る。
これらの事を考えると、各種コントロールをアップデートするには、
- NSWindowのupdateメソッドをオーバーライドする。
- windowに乗っている全てのview(control)に対して、NSUserInterfaceValidationsプロトコルに基づく更新作業を行う。
と言うのがCocoa的に正しいUI更新の方法かもしれない。
MenuやToolbarはデフォルトでこのような更新メカニズムが実装されている。 NSControlにそのような仕組みが無いのは、歴史的理由とCPUパワーの問題と思われる。
現状ではCPUパワーは有り余っている。よってイベントループ毎にcontrolを更新しても問題ないと、私は判断し実装する事にする。
参考文献
OpenStepリファレンス