objective-c の NSLog について、デバッグ時のみログを出力し、
リリースビルドではおとなしくさせる方法を知りたく、ググってみた。
いろいろなサイトで紹介されており、
次のようなマクロを定義すれば出来るとある。
#ifdef MYDEBUG #define MYLOG(...) NSLog(__VA_ARGS__) #else #define MYLOG(...) #endif
で、この、__VA_ARGS__ って一体何だ?
と思い、本家 gccの
ドキュメント
を探ってみると、
にずばりそのものの記述があった。
どうやら、printf みたいなことをマクロでやるためのものらしい。
C言語なんて もう何十年も前に K&R を読んだっきり、
全く勉強していなかったけど、いつのまにか進歩している!
デバッグする上では、この MYLOG マクロで充分なんだけど、どうせなら 、
NSLog を書いたクラスやメソッドの場所がわかればいいなあと思い、さらに
gccのページを探ってみると.....
みつけた!
__PRETTY_FUNCTION__
という変数が、それを使った場所のメソッド名や関数名を、
文字列として保持しているらしい。
じゃあ、ということで、メソッド名も出力してくれる、MYLOG2を書いてみた。
#define MYLOG2(fmt, ...) \ NSLog([@"%s " stringByAppendingString:(fmt)], \ __PRETTY_FUNCTION__, ##__VA_ARGS__)
__VA_ARGS__ の前の ## は、
VA_ARGS が空の時に、
',' の展開をさせないためのおまじないだ。
以下が、そのサンプルと実行結果。
# サンプルソース $ cat test-mylog.m // -*-mode:objc; coding:utf-8; -*- #import <Foundation/Foundation.h> #define MYLOG2(fmt, ...) \ NSLog([@"%s " stringByAppendingString:(fmt)], \ __PRETTY_FUNCTION__, ##__VA_ARGS__) @interface MyTest : NSObject { NSString *message; } @property (readwrite, copy) NSString *message; @end @implementation MyTest - (id) init { self = [super init]; if(self){message = nil;} return self; } - (void) dealloc { MYLOG2(@"dealloc!"); // Test for Empty VA_ARGS [self setMessage:nil]; [super dealloc]; } -(NSString *)message{ return message;} -(void)setMessage:(NSString *)m { NSString *fmt = @"message will change from %@ to %@."; if( ! m )fmt = @"message '%@' will release."; MYLOG2(fmt, message, m); m = [m copy]; [message release]; message = m; MYLOG2(@"message did change to %@", message); } @end int main(int argc, char *argv[]){ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; MyTest *test = [[[MyTest alloc] init] autorelease]; [test setMessage:@"Hello"]; [pool release]; return 0; } # コンパイル $ gcc test-mylog.m -framework Foundation # 実行 $ ./a.out -[MyTest setMessage:] message will change from (null) to Hello. -[MyTest setMessage:] message did change to Hello -[MyTest dealloc] dealloc! -[MyTest setMessage:] message 'Hello' will release. -[MyTest setMessage:] message did change to (null) $
ちゃんと、[ クラス名 セレクター ] が表示される。
調子に乗って、MYLOG2と同じやり方で、alert の簡易マクロを書いてみた。
#define MYALERT_OK(title, ...) \ ([[NSAlert alertWithMessageText:(title) \ defaultButton:@"OK" \ alternateButton:nil otherButton:nil \ informativeTextWithFormat:__VA_ARGS__ ] runModal]) #define MYALERT_CANCEL_OK(title, ...) \ ([[NSAlert alertWithMessageText:(title) \ defaultButton:@"CANCEL" \ alternateButton:@"OK" otherButton:nil \ informativeTextWithFormat:__VA_ARGS__ ] runModal])
このマクロは、こんな感じで使える。
if( ! [[NSFileManager defaultManager] fileExistsAtPath:fileName] ){ int choice = MYALERT_CANCEL_OK(@"Confirm", @"Create new file:%@ ?",fileName); if(choice == NSAlertDefaultReturn){ MYALERT_OK(@"CANCELED!",@""); ..................; }else{ ..................; } }
これなら、javascript 並みにたやすく alert デバッグができそうだ。
ところが、この MYLOG2 と MYALERT_CANCEL_OK をいろいろと使っているうち
に、うまくいかない場合を見つけてしまった。
例えば、
MYLOG2([NSString stringWithFormat:@"%@ %%s",
@"Program name is"], argv[0]);
と書くと、これは、
@"Program name is"], argv[0]);
error: syntax error before ‘)’ token
error: syntax error before ‘]’ token
のコンパイルエラーになってしまう。error: syntax error before ‘]’ token
でも、最初の引数を、次のように括弧で囲むとエラーは無くなる。
MYLOG2( ([NSString stringWithFormat:@"%@ %%s",
@"Program name is"]), argv[0]);
@"Program name is"]), argv[0]);
どうやら、マクロの実引数に、stringWithFormat: のような
VA_ARGS形式のセレクターを使うと、そのままでは、 ',' が変な風に解釈されてしまい、
コンパイルエラーになってしまうみたいだ。
でも、その実引数が、仮引数の ... 部分にマッチする場合は何の問題もない。
とりあえずの解決策は、( ) で囲め、ということか。
このあたりはさっぱりわからない。でも、マクロは楽しい。
マクロを使って、こんなことも出来てしまった。
$cat test-indexset.m // -*-mode:objc; coding:utf-8; -*- #import <Foundation/Foundation.h> /* * Ruby の NSIndexSet.each{|val| ...} 相当のつもり。 * VA_ARGS を使っているが、実際には、3引数のマクロとして使う */ #define MYEACH_INDEXSET(idx,idxvalue, ...) \ { \ NSIndexSet *___tmpidx___ = (idx); \ NSUInteger idxvalue = [___tmpidx___ firstIndex]; \ while( idxvalue != NSNotFound ){ \ __VA_ARGS__ \ idxvalue = [___tmpidx___ indexGreaterThanIndex:idxvalue]; \ } \ } int main(int argc, char *argv[]){ NSAutoreleasePool *pool; pool = [[NSAutoreleasePool alloc] init]; NSArray *strList = [NSArray arrayWithObjects:@"zero",@"one",@"two" ,@"three",@"four",@"five", nil]; NSMutableIndexSet *idxSet = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0,4)]; [idxSet addIndex:10];[idxSet addIndex:5]; MYEACH_INDEXSET(idxSet, val, { NSString *s = @"?"; if( val < [strList count]){ s = [strList objectAtIndex:val]; } NSLog(@"strList[%d]=%@",val,s); }); [pool release]; return 0; } $ gcc test-indexset.m -framework Foundation $ ./a.out strList[0]=zero strList[1]=one strList[2]=two strList[3]=three strList[5]=five strList[10]=? $
僕としては、ruby のイテレータもどきを、
objective-c で実装したつもりだ。
マクロの実引数に { } で囲んだ文を指定して、さらに、展開も文なんだけど、
とりあえずサンプルは動作している。
でも、こんなの、たぶん問題だらけなんだろうなあ....
確か、昔読んだCの落とし穴本に、
「マクロは文ではない」と書いてあったと思う。
0 件のコメント:
コメントを投稿