[XCODE] iPhoneアプリが予期せぬエラーで異常終了する際に、そのログを取得しておく方法
こんにちは、@yoheiMuneです。
本日は、Evernoteアプリなどにある機能で、アプリが異常終了した際のログを保存しておき、 次の起動時に異常終了したことを検知したり、そのログを取得する方法をブログに書きたいと思います。
iPhone実機上で動いている場合にも、異常終了した場合の詳細なログを取得しておけるとデバッグがすごく楽になります。 今回はそのための方法を書きたいと思います。
AppDelegateにエラーキャッチの仕組みと、エラーをキャッチした際のログの取得/保存する仕組みを導入します。
(AppDelegate.mでの実装)
こんな簡単な実装で、異常終了時のログを採取することが可能となります。
例えば、以下のような悪いコードを書いた場合には、
上記ログを見て、自身のクラス(今回の例では、iPhoneStudyプロジェクトのShowErrorViewController)で、NSArrayのindex不正で落ちたことがわかります。
実際の実装では異常終了したNSArrayが何に使われているものでどのような情報をつめているはずかを事前に把握していれば、よりデバッグしやすいかもしれません。
上記のNSArrayのindex不正もそうですが、ネットワーク処理やマルチスレッドなどの実装を行うと意味不明に落ちる場合もあり、再現させるまでが大変です。
今後もより開発しやすい環境を目指しいろいろと調べて、ブログに書きたいと思います。
最後までご覧頂きましてありがとうございました。
本日は、Evernoteアプリなどにある機能で、アプリが異常終了した際のログを保存しておき、 次の起動時に異常終了したことを検知したり、そのログを取得する方法をブログに書きたいと思います。
iPhoneアプリで異常終了した際のログを取得するメリット
iPhoneアプリの開発経験があれば、誰しも異常終了した際のデバッグに困ったことが多いのではないでしょうか? 特にリリース後にユーザーから指摘された異常終了を再現するとなると、ちょいと大変。iPhone実機上で動いている場合にも、異常終了した場合の詳細なログを取得しておけるとデバッグがすごく楽になります。 今回はそのための方法を書きたいと思います。
異常終了時にエラーログを取得して保存しておく実装方法
具体的な実装方法は以下の通りになります。AppDelegateにエラーキャッチの仕組みと、エラーをキャッチした際のログの取得/保存する仕組みを導入します。
(AppDelegate.mでの実装)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 〜 途中省略 〜 // エラー追跡用の機能を追加する。 NSSetUncaughtExceptionHandler(&exceptionHandler); return YES; } // 異常終了を検知した場合に呼び出されるメソッド void exceptionHandler(NSException *exception) { // ここで、例外発生時の情報を出力します。 // NSLog関数でcallStackSymbolsを出力することで、 // XCODE上で開発している際にも、役立つスタックトレースを取得できるようになります。 NSLog(@"%@", exception.name); NSLog(@"%@", exception.reason); NSLog(@"%@", exception.callStackSymbols); // ログをUserDefaultsに保存しておく。 // 次の起動の際に存在チェックすれば、前の起動時に異常終了したことを検知できます。 NSString *log = [NSString stringWithFormat:@"%@, %@, %@", exception.name, exception.reason, exception.callStackSymbols]; [[NSUserDefaults standardUserDefaults] setValue:log forKey:@"failLog"]; }
こんな簡単な実装で、異常終了時のログを採取することが可能となります。
例えば、以下のような悪いコードを書いた場合には、
// わざとエラーが発生するように、実装する。 NSArray *array = [NSArray array]; [array objectAtIndex:0];以下のような、エラーログが表示されます。
2013-01-20 22:10:31.090 iPhoneStudy[1366:c07] NSRangeException 2013-01-20 22:10:31.091 iPhoneStudy[1366:c07] *** -[__NSArrayI objectAtIndex:]: index 0 beyond bounds for empty array 2013-01-20 22:10:31.094 iPhoneStudy[1366:c07] ( 0 CoreFoundation 0x021bc02e __exceptionPreprocess + 206 1 libobjc.A.dylib 0x01ec9e7e objc_exception_throw + 44 2 CoreFoundation 0x02171b44 -[__NSArrayI objectAtIndex:] + 196 3 iPhoneStudy 0x0002292e -[ShowErrorViewController viewDidLoad] + 142 4 UIKit 0x0113d817 -[UIViewController loadViewIfRequired] + 536 5 UIKit 0x0113d882 -[UIViewController view] + 33 6 UIKit 0x0113db2a -[UIViewController contentScrollView] + 36 7 UIKit 0x01154ef5 -[UINavigationController _computeAndApplyScrollContentInsetDeltaForViewController:] + 36 8 UIKit 0x01154fdb -[UINavigationController _layoutViewController:] + 43 9 UIKit 0x01155286 -[UINavigationController _updateScrollViewFromViewController:toViewController:] + 254 10 UIKit 0x01155381 -[UINavigationController _startTransition:fromViewController:toViewController:] + 72 11 UIKit 0x01155eab -[UINavigationController _startDeferredTransitionIfNeeded:] + 386 12 UIKit 0x011564a3 -[UINavigationController pushViewController:transition:forceImmediate:] + 1030 13 UIKit 0x01156098 -[UINavigationController pushViewController:animated:] + 62 14 iPhoneStudy 0x00003b14 -[RootViewController tableView:didSelectRowAtIndexPath:] + 388 15 UIKit 0x0110b8d5 -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1194 16 UIKit 0x0110bb3d -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 201 17 Foundation 0x00c7de83 __NSFireDelayedPerform + 380 18 CoreFoundation 0x0217b376 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 22 19 CoreFoundation 0x0217ae06 __CFRunLoopDoTimer + 534 20 CoreFoundation 0x02162a82 __CFRunLoopRun + 1810 21 CoreFoundation 0x02161f44 CFRunLoopRunSpecific + 276 22 CoreFoundation 0x02161e1b CFRunLoopRunInMode + 123 23 GraphicsServices 0x02f977e3 GSEventRunModal + 88 24 GraphicsServices 0x02f97668 GSEventRun + 104 25 UIKit 0x0105c65c UIApplicationMain + 1211 26 iPhoneStudy 0x00001f63 main + 67 27 iPhoneStudy 0x00001ed5 start + 53 ) 2013-01-20 22:10:31.095 iPhoneStudy[1366:c07] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 0 beyond bounds for empty array' *** First throw call stack: (0x21bc012 0x1ec9e7e 0x2171b44 0x2292e 0x113d817 0x113d882 0x113db2a 0x1154ef5 0x1154fdb 0x1155286 0x1155381 0x1155eab 0x11564a3 0x1156098 0x3b14 0x110b8d5 0x110bb3d 0xc7de83 0x217b376 0x217ae06 0x2162a82 0x2161f44 0x2161e1b 0x2f977e3 0x2f97668 0x105c65c 0x1f63 0x1ed5) libc++abi.dylib: terminate called throwing an exception (lldb)
上記ログを見て、自身のクラス(今回の例では、iPhoneStudyプロジェクトのShowErrorViewController)で、NSArrayのindex不正で落ちたことがわかります。
実際の実装では異常終了したNSArrayが何に使われているものでどのような情報をつめているはずかを事前に把握していれば、よりデバッグしやすいかもしれません。
最後に
iPhoneアプリを開発する上で、異常終了に対するデバッグ処理の効率化は大変重要かと思います。上記のNSArrayのindex不正もそうですが、ネットワーク処理やマルチスレッドなどの実装を行うと意味不明に落ちる場合もあり、再現させるまでが大変です。
今後もより開発しやすい環境を目指しいろいろと調べて、ブログに書きたいと思います。
最後までご覧頂きましてありがとうございました。