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 件のコメント:
コメントを投稿