viewWillLoad/viewDidLoad on NSViewController
UIKitのUIViewControllerには
- (void)viewWillLoad; - (void)viewDidLoad;
が定義されています。UIViewControllerのサブクラスにおいて、これらのメソッドは頻繁にオーバーライドされます。
しかし、AppKitのNSViewControllerには存在しません。UIKitに慣れた人がOS XプログラミングでAppKitを触ると、「おい、view(Will|Did)Loadがないじゃねえか!」となります。なりました。
Stack Overflowに、関連するトピックがあります。*1
そこでは、このような解決法が提示されています。
- (void)viewWillLoad { } - (void)viewDidLoad { } - (void)loadView { [self viewWillLoad]; [super loadView]; [self viewDidLoad]; }
しかし、コメント*2で、「Appleが今後 -viewWillLoad と -viewDidLoad を追加したら面倒臭いことになるで!」と指摘されています。
この指摘は至極もっともです。なので、こういうときはアプリケーションのクラスプリフィクスを付けておくのが無難ではないかと思います。
クラスプリフィクスが'MY'のときは以下のようになります。
- (void)MYViewWillLoad { } - (void)MYViewDidLoad { } - (void)loadView { [self MYViewWillLoad]; [super loadView]; [self MYViewDidLoad]; }
クラスプリフィクスを付けるのが嫌ならば、以下のようにするのも良いと思います。
- (void)viewWillLoad { if ([super respondsToSelector:@selector(viewWillLoad)]) { [super performSelector:@selector(viewWillLoad)]; } // Code } - (void)viewDidLoad { if ([super respondsToSelector:@selector(viewDidLoad)]) { [super performSelector:@selector(viewDidLoad)]; } // Code } - (void)loadView { [self viewWillLoad]; [super loadView]; [self viewDidLoad]; }
Objective-Cのボトルネック検出
パフォーマンスの改善には、ボトルネックの検出が欠かせません。
どの処理に時間がかかっているのか判別するため、処理の経過時間を記録します。
stackoverflow*1では以下のような方法が提案されています。
NSDate *start = [NSDate date];
// do stuff...
NSTimeInterval timeInterval = [start timeIntervalSinceNow];
これで経過時間を取得できます。
が、しかし。「1回だけ経過時間を取得する」なんてことは、あまりありません。
たとえば、「あるメソッド内で行われている処理の中で、一番重い処理を見つけたい」といったときには、メソッドの処理を5個くらいに分割してそれぞれの経過時間を取得し、大雑把なアタリをつける方が効率的です。
つまり、経過時間を取得する処理が複数回おこなわれます。
しかし、経過時間を取得するたびにログを吐いていると、NSLog自体がかなり重い処理のため、計測結果が大きく狂います。これを避けるためには、経過時間を配列で保持しておいて、すべて取得してからまとめてログを吐いてやる必要があります。
意外と面倒くさいです。
面倒くさいコードを何度も書きたくないので、マクロ化しました。
#define PTStart \ int PTIndex = 0;\ double PTElapledTimes[100] = {0.0f};\ int PTLines[100] = {0};\ char PTComments[100][30] = {'\n'};\ int PTStartLine = __LINE__;\ CFAbsoluteTime PTStartTime = CFAbsoluteTimeGetCurrent();\ CFAbsoluteTime PTCurrent = CFAbsoluteTimeGetCurrent(); #define PTGetDuration(comment) \ PTLines[PTIndex] = __LINE__;\ strcpy(PTComments[PTIndex], comment);\ PTElapledTimes[PTIndex++] = CFAbsoluteTimeGetCurrent() - PTCurrent;\ PTCurrent = CFAbsoluteTimeGetCurrent(); #define PTPrintResult \ NSLog(@"%s", __PRETTY_FUNCTION__);\ NSLog(@"total duration: %f", CFAbsoluteTimeGetCurrent() - PTStartTime);\ for (int i = 0; i < PTIndex; i++) {\ int sl = i == 0 ? PTStartLine : PTLines[i-1];\ NSLog(@"%04d-%04d\telapsed:%.10f\t%s", sl, PTLines[i], PTElapledTimes[i], PTComments[i]);\ }
関数ではなくマクロなので、計測対象コードの中にインライン展開されることとなり、名前空間が分かれません。そのため、一応変数にPrefixをつけています。
PTStartで取得開始、経過時間を取得したい行にPTGetDuration(comment)、最後にPTPrintResultで出力します。
出力内容は以下の通り、ラインナンバー・経過時間・コメントとなっています。
PTStart takz(1, 0, 0); PTGetDuration("1,0,0") takz(10, 0, 0); PTGetDuration("10,0,0") takz(12, 6, 0); PTGetDuration("12,6,0") takz(18, 9, 0); PTGetDuration("18,9,0") PTPrintResult /* 出力 */ PerformanceTimer[5692:303] int main(int, const char **) PerformanceTimer[5692:303] total duration: 0.053143 PerformanceTimer[5692:303] 0022-0026 elapsed:0.0000050068 1,0,0 PerformanceTimer[5692:303] 0026-0030 elapsed:0.0000000000 10,0,0 PerformanceTimer[5692:303] 0030-0034 elapsed:0.0002049804 12,6,0 PerformanceTimer[5692:303] 0034-0038 elapsed:0.0512049794 18,9,0
Github:
https://github.com/oropon/PerformanceTimer
*1:http://stackoverflow.com/questions/741830/getting-the-time-elapsed-objective-c
悩ましきコード整形
論理演算子のコード整形ってなんだか悩ましいですね。
まずは例を。UIViewのautoresizingMaskを設定するコード。いくつかの設定値の論理和を取ります。
view.autoresizingMask = ( UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight );
ここで、UIViewAutoresizingFlexibleHeightのみをコメントアウトしたいと思って、
view.autoresizingMask = (
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleWidth |
// UIViewAutoresizingFlexibleHeight
);
などと書くと、構文エラーでコンパイルが通らなくなります。UIViewAutoresizingFlexibleWidthの横に付いている、論理和演算子"|"の右辺がないためです。
同様に、以下のように行を昇降させた場合もコンパイルが通りません。
view.autoresizingMask = ( UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | );
一方、配列ではこんなことはありません。このような配列で、Sundayのみをコメントアウトしても全く問題ありません。
NSArray* dayNames = @[ @"Monday", @"Tuesday", @"Wednesday", @"Thursday", @"Friday", @"Saturday", @"Sunday", ];
NSArray* dayNames = @[ @"Monday", @"Tuesday", @"Wednesday", @"Thursday", @"Friday", @"Saturday", // @"Sunday", ];
(Objective-)Cに限らずともたいていの場合、配列の最終要素の後につけたコンマは無視されます。*1
そのため任意の要素(行)を手軽にコメントアウトしたり、順番を変えたりできるわけですが、これは非常に便利です。
もしも論理和計算でこれを実現しようと思うと、末尾要素にゼロを置く必要があります。以下のとおりですが、スマートとは思えません。
view.autoresizingMask = ( UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | 0);// 要素末尾にゼロを置く
知らない人が見たら「なんでこんなところにゼロがあるねん。。アホ?」って思うでしょうね。
同様に複数の条件をもつIF文で書くと、以下のようになります。「何がしたいんだ。。?アホ?」って突っ込まれることでしょう。
if ( cond0 || cond1 || cond2 || NO) { //... }
うーん、悩ましい。
*1:IE6, 7は死ね。
RecursiveDescriptionについて
UIKitのUIViewクラスには、ビューのヒエラルキーを再帰的にダンプする -(NSString*)recursiveDescription というプライベートAPIが用意されています。
しかし、どうもこれはAppKitのNSViewクラスには定義されていないようです。
ないので書きました。UIViewのrecursiveDescriptionとまったく同じ挙動をするはずです。
#if TARGET_OS_IPHONE @interface UIView (RecursiveDescription) #else @interface NSView (RecursiveDescription) #endif #ifdef DEBUG - (NSString*)_recursiveDescription; #endif @end #if TARGET_OS_IPHONE @implementation UIView (RecursiveDescription) #else @implementation NSView (RecursiveDescription) #endif - (NSString*)_recursiveDescription { return _recursiveDescription(self, 0); } NSString* _recursiveDescription(id view, NSUInteger depth) { NSMutableString* subviewsDescription; subviewsDescription = [NSMutableString string]; for (id v in [view subviews]) { [subviewsDescription appendString:_recursiveDescription(v, depth+1)]; } NSMutableString* layout; layout = [NSMutableString string]; for (int i = 0; i < depth; i++) { [layout appendString:@" | "]; } return [NSString stringWithFormat:@"%@%@\n%@", layout, [view description], subviewsDescription]; } @end
gist: https://gist.github.com/oropon/5159805
DEBUGマクロは別に必要ない気がしてきた。
ついでに一行ブロックバージョンも書いておきました。ちょっと確認したいときにこれ貼り付けて確認して、デバッグできたら消せば良い。
NSString*(^__block __weak r)(id,int)=^(id v,int d){NSMutableString*m,*l;m=[NSMutableString string];l=[NSMutableString string];for(id _ in [v subviews])[m appendString:r(_,d+1)];for(int i=0;i<d;i++)[l appendString:@" | "];return[NSString stringWithFormat:@"%@%@\n%@",l,[v description], m];}; NSLog(@"\n%@", r(<#target_view#>, 0));
REKitを使ってみた
REKit自体の紹介は開発者に譲ります。これはもっと広まると思う。
https://github.com/zuccoi/REKit (ProjectのGitHubページ)
https://github.com/zuccoi/REKit/blob/master/README_ja.md (日本語README)
http://runlooprun.wordpress.com/2013/02/12/rekit-intro/ (開発者による紹介、解説)
簡単に言うと、
1. ブロックによる特異メソッドの追加ができる
2. ブロックによるハンドラ付きでオブザーバの追加ができる
といったもの。。だと思います。あってますよね?
活用例はREADME、およびGitHubで公開されているプロジェクトに含まれているサンプルコードが非常にわかりやすいです。
「お、Blocksサポートしたのか!だったらこんなこともできたらいいな!」とみんなが妄想していた機能が実現しました!って感じでワクワクします。
ついでに、ちょっと遊んでみました。
動的にメソッドを追加できるのだから、ついでに可搬性を持たせてみると面白いのではと書いたのが以下。
// NSObject+REResponder.h - (NSInvocation*)invocationForSelector:(SEL)selector withKey:(id)inKey usingBlock:(id)block; // NSObject+REResponder.m - (NSInvocation*)invocationForSelector:(SEL)selector withKey:(id)inKey usingBlock:(id)block { // Responds to given selector [self respondsToSelector:selector withKey:inKey usingBlock:block]; // Get invocation NSInvocation *invocation; NSMethodSignature *signature; signature = [self methodSignatureForSelector:selector]; invocation = [NSInvocation invocationWithMethodSignature:signature]; // Configure invocation invocation.target = self; invocation.selector = selector; return invocation; }
使う方はこんな感じ。
NSInvocation* helloREKitInvocation; NSString* name = @"REKit"; helloREKitInvocation = ^(NSInvocation* invocation) { [invocation setArgument:(void*)&name atIndex:2]; return invocation; }([self invocationForSelector:@selector(helloREKit) withKey:nil usingBlock:^(id receiver, NSString* name) { NSLog(@"Hello, %@!!", name); }]); [helloREKitInvocation invoke]; #=>Hello, REKit!! NSInvocation* putsWeekInvocation; NSArray* weekStrs; weekStrs = @[@"Mon", @"Tue", @"Wed", @"Thu", @"Fri", @"Sat", @"Sun"]; putsWeekInvocation = [weekStrs invocationForSelector:@selector(putsWeek) withKey:nil usingBlock:^(id receiver) { for(NSString* w in receiver) { NSLog(@"%@", w); } }]; [putsWeekInvocation invoke]; #=> Mon Tue Wed Thu Fri Sat Sun
ほとんどコードを書いていないのに、その場で、存在しないはずのメソッドを発火するNSInvocationインスタンスが手に入る!
これは色々と面白いことができそうです。
「ベジェ曲線とはなんぞや」を説明するアプリケーション
なめらかな曲線を描くならベジェ曲線。
プログラマ、マークアップエンジニア、デザイナetc..
IT土方でベジェ曲線に縁がない職の方が少ないんではないかってくらい身近な存在ベジェ曲線。
ベジェ曲線についてプレゼンする必要に迫られて、ざっくりとMacアプリを作りました。
「ベジェ曲線ってこんなものだよ。分割はこんなかんじで、長さの取得はこんな感じだよ。」
っていう簡単なデモのためのアプリですね。
いつかコードを再利用することもあろうかと、いちおうGitHubに上げておきました。
動かして、コードを見れば、ベジェ曲線とNSBezierPath(/UIBezierPath)の一番基礎的なところは抑えられるかと思います。
https://github.com/oropon/BezierCurveSample
なお、ベジェ曲線について色々と調べる際には
http://www.tinaja.com/cubic01.shtml
が情報豊富です。
パラメータtからポイントx, yを求めるのは簡単だけど、ポイントx,yが与えられた時にtを求めるにはどうするんだ?とか、円や楕円に近似したベジェ曲線を描くには?とか、そういった疑問の答えやヒントが盛り沢山です。
あまりにも美しいコードを書いてしまった
#import <Foundation/Foundation.h> @interface _ : NSObject typedef _* (^__)(); typedef __ (^___)(); typedef ___ (^____)(); typedef ____ (^_____)(); typedef _____ (^______)(); @property (readonly) _* _; @property (readonly) __ __; @property (readonly) ___ ___; @property (readonly) ____ ____; @property (readonly) _____ _____; @property (readonly) ______ ______; + (_*)_; - (void)_______; @end @implementation _ + (_ *)_ { return [[_ alloc] init]; } - (void)_______ { NSLog(@"_______"); return; } - (_ *)_ { return self; } - (__)__ { return ^_*(){ return self._; }; } - (___)___ { return ^__(){ return self.__; }; } - (____)____ { return ^___(){ return self.___; }; } - (_____)_____ { return ^____(){ return self.____; }; } - (______)______ { return ^_____(){ return self._____; }; } @end int main(int argc, const char * argv[]) { @autoreleasepool { _* __; __ = [_ _]; __.______()()()()()._____()()()().____()()().___()().__()._._______; } return 0; }
クラス名とプロパティとクラスメソッドの名前が被っていますが問題ないようです。
なんだか心が洗われますね。