tokoromのその他の日記
- vim関連: vimまっしぐら★
- それ以外: 寄り道ばかりの お勉強日記★
2011-12-25
uncaught exceptionが発生した場所を確認する
debug | |
uncaught exception でアプリリセット
uncaught exceptionでmain.mのUIApplicationMainのところでリセットが発生しているときに、以下のようなログが出て、
2011-12-26 02:14:41.837 test[64894:f803] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]' *** First throw call stack: (0x12e2052 0x1896d0a 0x12ce674 0x4a7f7 0x2ab7 0x22d9d6 0x22e8a6 0x23d743 0x23e1f8 0x231aa9 0x21cffa9 0x12b61c5 0x121b022 0x121990a 0x1218db4 0x1218ccb 0x22e2a7 0x22fa9b 0x27d9 0x1fc5)
「で、けっきょくこのExceptionどこで発生してんねん」と思ってしまうことがあります*1。
ふつーにぱっと見で認識できるスタックトレースとか出してくれればいいのにねって。
exception をキャッチしてデバッグ表示
それなら、uncaughtなexceptionをキャッチしてあげようということで、UIApplicationMainのところをtry/catchで囲ってデバッグ表示を追加してみました。
int main(int argc, char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; int retVal; @try { retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } @catch (NSException *exception) { NSLog(@"%@", [exception callStackSymbols]); //< ★1 @throw exception; //< ★2 } @finally { [pool release]; } return retVal; }
★1
どうもiOS4.0から、NSExceptionにcallStackSymbolsという便利メソッドが追加されたようなのでそれを利用。
そうすると、uncaughtなexceptionをキャッチして、以下ようにそのexceptionが発生するまでの経緯がログ出力されます。
2011-12-26 02:27:18.933 test[65600:f803] ( 0 CoreFoundation 0x012e206e __exceptionPreprocess + 206 1 libobjc.A.dylib 0x01896d0a objc_exception_throw + 44 2 CoreFoundation 0x012ce674 -[__NSArrayI objectAtIndex:] + 196 3 test 0x0004a6e7 -[RootViewController init] + 215 4 test 0x000029a7 -[AppDelegate application:didFinishLaunchingWithOptions:] + 567 5 UIKit 0x0022d9d6 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1292 6 UIKit 0x0022e8a6 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 508 7 UIKit 0x0023d743 -[UIApplication handleEvent:withNewEvent:] + 1027 8 UIKit 0x0023e1f8 -[UIApplication sendEvent:] + 68 9 UIKit 0x00231aa9 _UIApplicationHandleEvent + 8196 10 GraphicsServices 0x021cffa9 PurpleEventCallback + 1274 11 CoreFoundation 0x012b61c5 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 53 12 CoreFoundation 0x0121b022 __CFRunLoopDoSource1 + 146 13 CoreFoundation 0x0121990a __CFRunLoopRun + 2218 14 CoreFoundation 0x01218db4 CFRunLoopRunSpecific + 212 15 CoreFoundation 0x01218ccb CFRunLoopRunInMode + 123 16 UIKit 0x0022e2a7 -[UIApplication _run] + 576 17 UIKit 0x0022fa9b UIApplicationMain + 1175 18 test 0x0000258b main + 187 19 test 0x00001d65 start + 53 )
これを見れば「RootViewControllerのinitメソッド内にダメなコードがあるんだね」ということがすぐに分かるようになります。
★2
ちなみにexceptionをキャッチしっぱなしだとアプリ的には異常がなかったという扱いになってしまうので、同じexceptionをそのままthrowしておきます。
DEBUGビルド時にだけこれを有効に
DEBUGビルド時にだけこれを有効にしたい場合には、以下のように「#ifdef DEBUG」で囲ってやるのが良いかもしれません。
int main(int argc, char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; int retVal; #ifdef DEBUG @try { #endif retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); #ifdef DEBUG } @catch (NSException *exception) { NSLog(@"%@", [exception callStackSymbols]); @throw exception; } @finally { #endif [pool release]; #ifdef DEBUG } #endif return retVal; }
ARCを利用する場合
ARCを利用する場合はもうちょっとシンプルにできる。
int main(int argc, char *argv[]) { int retVal; @autoreleasepool { #ifdef DEBUG @try { #endif retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); #ifdef DEBUG } @catch (NSException *exception) { NSLog( @"%@", [exception callStackSymbols] ); @throw exception; } #endif } return retVal; }
でも、こんなごちゃごちゃ書かなくてもよいかもしれない
ちなみに、uncaught exceptionを制御するための「NSSetUncaughtExceptionHandler」といった関数もあるようで、こちらを使えばもっとスマートな方法があるかもしれません*2。
もう眠くなったのと、↑でも特に困らなかったので今日はここまで。

iPhoneプログラミング UIKit詳解リファレンス
詳解 EZアプリ(BREW)プログラミング