Hatena::Groupiphone-dev

iPhoneアプリ開発まっしぐら★ このページをアンテナに追加 RSSフィード

引っ越し後の日記はコチラです

tokoromのその他の日記

2011-12-25

uncaught exceptionが発生した場所を確認する

| 02:42 | はてなブックマーク -  uncaught exceptionが発生した場所を確認する - iPhoneアプリ開発まっしぐら★

uncaught exception でアプリリセット

uncaught exceptionでmain.mUIApplicationMainのところでリセットが発生しているときに、以下のようなログが出て、

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

もう眠くなったのと、↑でも特に困らなかったので今日はここまで。

*1:そもそもこのExceptionの発生場所をすぐに特定できる方法を既知のかたは是非フォローお願いしますm(_ _)m

*2:こちらも既知のかた是非フォローをお願いします

TakuTaku2016/05/10 16:26参考になりました。

でも、全てのエラーをキャッチしないみたいですね。
残念ながらNSSetUncaughtExceptionHandlerも全てのエラーをキャッチしないみたいですね
例えば、
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self performSelector:@selector(nonExistantMethod) withObject:nil afterDelay:4.0];
}
となるとキャッチされますがアプリケーションもcrashしますね
-[UncaughtExceptionsAppDelegate nonExistantMethod]: unrecognized selector sent to instance」