虽然大家都不愿意看到程序崩溃,但可能崩溃是每个应用必须面对的现实,既然崩溃已经发生,无法阻挡了,那我们就让它崩也崩得淡定点吧。
iOS SDK中提供了一个现成的函数 NSSetUncaughtExceptionHandler 用来做异常处理,但功能非常有限,而引起崩溃的大多数原因如:内存访问错误,重复释放等错误就无能为力了,因为这种错误它抛出的是Signal,所以必须 要专门做Signal处理。首先定义一个UncaughtExceptionHandler类,.h头文件的代码如下:
- #import <UIKit/UIKit.h>
- @interface UncaughtExceptionHandler : NSObject
- {
- BOOL dismissed;
- }
- @end
- void InstallUncaughtExceptionHandler();
- 然后在.mm文件实现InstallUncaughtExceptionHandler(),如下:
- void InstallUncaughtExceptionHandler()
- {
- signal(SIGABRT, MySignalHandler);
- signal(SIGILL, MySignalHandler);
- signal(SIGSEGV, MySignalHandler);
- signal(SIGFPE, MySignalHandler);
- signal(SIGBUS, MySignalHandler);
- signal(SIGPIPE, MySignalHandler);
- }
这样,当应用发生错误而产生上述Signal后,就将会进入我们自定义的回调函数MySignalHandler。为了得到崩溃时的现场信息,还可以加入一些获取CallTrace及设备信息的代码,.mm文件的完整代码如下:
- #import "UncaughtExceptionHandler.h"
- #include <libkern/OSAtomic.h>
- #include <execinfo.h>
- NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
- NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
- NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";
- volatile int32_t UncaughtExceptionCount = 0;
- const int32_t UncaughtExceptionMaximum = 10;
- const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4;
- const NSInteger UncaughtExceptionHandlerReportAddressCount = 5;
- @implementation UncaughtExceptionHandler
- + (NSArray *)backtrace
- {
- void* callstack[128];
- int frames = backtrace(callstack, 128);
- char **strs = backtrace_symbols(callstack, frames);
- int i;
- NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
- for (
- i = UncaughtExceptionHandlerSkipAddressCount;
- i < UncaughtExceptionHandlerSkipAddressCount +
- UncaughtExceptionHandlerReportAddressCount;
- i++)
- {
- [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
- }
- free(strs);
- return backtrace;
- }
- - (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex
- {
- if (anIndex == 0)
- {
- dismissed = YES;
- }
- }
- - (void)handleException:(NSException *)exception
- {
- UIAlertView *alert =
- [[[UIAlertView alloc]
- initWithTitle:NSLocalizedString(@"Unhandled exception", nil)
- message:[NSString stringWithFormat:NSLocalizedString(
- @"You can try to continue but the application may be unstable.\n"
- @"%@\n%@", nil),
- [exception reason],
- [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]]
- delegate:self
- cancelButtonTitle:NSLocalizedString(@"Quit", nil)
- otherButtonTitles:NSLocalizedString(@"Continue", nil), nil]
- autorelease];
- [alert show];
- CFRunLoopRef runLoop = CFRunLoopGetCurrent();
- CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
- while (!dismissed)
- {
- for (NSString *mode in (NSArray *)allModes)
- {
- CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
- }
- }
- CFRelease(allModes);
- NSSetUncaughtExceptionHandler(NULL);
- signal(SIGABRT, SIG_DFL);
- signal(SIGILL, SIG_DFL);
- signal(SIGSEGV, SIG_DFL);
- signal(SIGFPE, SIG_DFL);
- signal(SIGBUS, SIG_DFL);
- signal(SIGPIPE, SIG_DFL);
- if ([[exception name] isEqual:UncaughtExceptionHandlerSignalExceptionName])
- {
- kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]);
- }
- else
- {
- [exception raise];
- }
- }
- @end
- NSString* getAppInfo()
- {
- NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@)\nDevice : %@\nOS Version : %@ %@\nUDID : %@\n",
- [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"],
- [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
- [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
- [UIDevice currentDevice].model,
- [UIDevice currentDevice].systemName,
- [UIDevice currentDevice].systemVersion,
- [UIDevice currentDevice].uniqueIdentifier];
- NSLog(@"Crash!!!! %@", appInfo);
- return appInfo;
- }
- void MySignalHandler(int signal)
- {
- int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
- if (exceptionCount > UncaughtExceptionMaximum)
- {
- return;
- }
- NSMutableDictionary *userInfo =
- [NSMutableDictionary
- dictionaryWithObject:[NSNumber numberWithInt:signal]
- forKey:UncaughtExceptionHandlerSignalKey];
- NSArray *callStack = [UncaughtExceptionHandler backtrace];
- [userInfo
- setObject:callStack
- forKey:UncaughtExceptionHandlerAddressesKey];
- [[[[UncaughtExceptionHandler alloc] init] autorelease]
- performSelectorOnMainThread:@selector(handleException:)
- withObject:
- [NSException
- exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
- reason:
- [NSString stringWithFormat:
- NSLocalizedString(@"Signal %d was raised.\n"
- @"%@", nil),
- signal, getAppInfo()]
- userInfo:
- [NSDictionary
- dictionaryWithObject:[NSNumber numberWithInt:signal]
- forKey:UncaughtExceptionHandlerSignalKey]]
- waitUntilDone:YES];
- }
- void InstallUncaughtExceptionHandler()
- {
- signal(SIGABRT, MySignalHandler);
- signal(SIGILL, MySignalHandler);
- signal(SIGSEGV, MySignalHandler);
- signal(SIGFPE, MySignalHandler);
- signal(SIGBUS, MySignalHandler);
- signal(SIGPIPE, MySignalHandler);
- }
在应用自身的 didFinishLaunchingWithOptions 前,加入一个函数:
- - (void)installUncaughtExceptionHandler
- {
- InstallUncaughtExceptionHandler();
- }
最后,在 didFinishLaunchingWithOptions 中加入这一句代码就行了:
- [self InstallUncaughtExceptionHandler];
现在,基本上所有崩溃都能Hold住了。崩溃时将会显示出如下的对话框:
这样在崩溃时还能从容地弹出对话框,比起闪退来,用户也不会觉得那么不爽。然后在下次启动时还可以通过邮件来发送Crash文件到邮箱,这就看各个应用的需求了。