如何让iOS应用从容地崩溃

移动开发 iOS
我知道程序崩溃是大家都不愿意见到的问题,但是既然崩溃已经发生,无法阻挡了,那我们就让它崩也崩得淡定点吧。

虽然大家都不愿意看到程序崩溃,但可能崩溃是每个应用必须面对的现实,既然崩溃已经发生,无法阻挡了,那我们就让它崩也崩得淡定点吧。

iOS SDK中提供了一个现成的函数 NSSetUncaughtExceptionHandler 用来做异常处理,但功能非常有限,而引起崩溃的大多数原因如:内存访问错误,重复释放等错误就无能为力了,因为这种错误它抛出的是Signal,所以必须 要专门做Signal处理。首先定义一个UncaughtExceptionHandler类,.h头文件的代码如下:

  1. #import <UIKit/UIKit.h> 
  2. @interface UncaughtExceptionHandler : NSObject 
  3. BOOL dismissed; 
  4. @end 
  5. void InstallUncaughtExceptionHandler(); 
  6. 然后在.mm文件实现InstallUncaughtExceptionHandler(),如下: 
  7. void InstallUncaughtExceptionHandler() 
  8. signal(SIGABRT, MySignalHandler); 
  9. signal(SIGILL, MySignalHandler); 
  10. signal(SIGSEGV, MySignalHandler); 
  11. signal(SIGFPE, MySignalHandler); 
  12. signal(SIGBUS, MySignalHandler); 
  13. signal(SIGPIPE, MySignalHandler); 

这样,当应用发生错误而产生上述Signal后,就将会进入我们自定义的回调函数MySignalHandler。为了得到崩溃时的现场信息,还可以加入一些获取CallTrace及设备信息的代码,.mm文件的完整代码如下:

  1. #import "UncaughtExceptionHandler.h" 
  2. #include <libkern/OSAtomic.h> 
  3. #include <execinfo.h> 
  4. NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName"
  5. NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey"
  6. NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey"
  7. volatile int32_t UncaughtExceptionCount = 0; 
  8. const int32_t UncaughtExceptionMaximum = 10; 
  9. const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4; 
  10. const NSInteger UncaughtExceptionHandlerReportAddressCount = 5; 
  11. @implementation UncaughtExceptionHandler 
  12. + (NSArray *)backtrace 
  13.         void* callstack[128]; 
  14. int frames = backtrace(callstack, 128); 
  15. char **strs = backtrace_symbols(callstack, frames);   
  16. int i; 
  17. NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames]; 
  18. for ( 
  19. i = UncaughtExceptionHandlerSkipAddressCount; 
  20. i < UncaughtExceptionHandlerSkipAddressCount + 
  21. UncaughtExceptionHandlerReportAddressCount; 
  22. i++) 
  23. [backtrace addObject:[NSString stringWithUTF8String:strs[i]]]; 
  24. free(strs);   
  25. return backtrace; 
  26. - (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex 
  27. if (anIndex == 0) 
  28. dismissed = YES; 
  29. - (void)handleException:(NSException *)exception 
  30. UIAlertView *alert = 
  31. [[[UIAlertView alloc] 
  32. initWithTitle:NSLocalizedString(@"Unhandled exception", nil) 
  33. message:[NSString stringWithFormat:NSLocalizedString( 
  34. @"You can try to continue but the application may be unstable.\n" 
  35. @"%@\n%@", nil), 
  36. [exception reason], 
  37. [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]] 
  38. delegate:self 
  39. cancelButtonTitle:NSLocalizedString(@"Quit", nil) 
  40. otherButtonTitles:NSLocalizedString(@"Continue", nil), nil] 
  41. autorelease]; 
  42. [alert show];    
  43. CFRunLoopRef runLoop = CFRunLoopGetCurrent(); 
  44. CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);    
  45. while (!dismissed) 
  46. for (NSString *mode in (NSArray *)allModes) 
  47. CFRunLoopRunInMode((CFStringRef)mode, 0.001, false); 
  48. }    
  49. CFRelease(allModes); 
  50. NSSetUncaughtExceptionHandler(NULL); 
  51. signal(SIGABRT, SIG_DFL); 
  52. signal(SIGILL, SIG_DFL); 
  53. signal(SIGSEGV, SIG_DFL); 
  54. signal(SIGFPE, SIG_DFL); 
  55. signal(SIGBUS, SIG_DFL); 
  56. signal(SIGPIPE, SIG_DFL);    
  57. if ([[exception name] isEqual:UncaughtExceptionHandlerSignalExceptionName]) 
  58. kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue]); 
  59. else 
  60. [exception raise]; 
  61. @end 
  62. NSString* getAppInfo() 
  63.     NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@)\nDevice : %@\nOS Version : %@ %@\nUDID : %@\n"
  64.                           [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"], 
  65.                           [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"], 
  66.                           [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], 
  67.                           [UIDevice currentDevice].model, 
  68.                           [UIDevice currentDevice].systemName, 
  69.                           [UIDevice currentDevice].systemVersion, 
  70.                           [UIDevice currentDevice].uniqueIdentifier]; 
  71.     NSLog(@"Crash!!!! %@", appInfo); 
  72.     return appInfo; 
  73. void MySignalHandler(int signal) 
  74. int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount); 
  75. if (exceptionCount > UncaughtExceptionMaximum) 
  76. return
  77. NSMutableDictionary *userInfo = 
  78. [NSMutableDictionary 
  79. dictionaryWithObject:[NSNumber numberWithInt:signal] 
  80. forKey:UncaughtExceptionHandlerSignalKey]; 
  81. NSArray *callStack = [UncaughtExceptionHandler backtrace]; 
  82. [userInfo 
  83. setObject:callStack 
  84. forKey:UncaughtExceptionHandlerAddressesKey];    
  85. [[[[UncaughtExceptionHandler alloc] init] autorelease] 
  86. performSelectorOnMainThread:@selector(handleException:) 
  87. withObject: 
  88. [NSException 
  89. exceptionWithName:UncaughtExceptionHandlerSignalExceptionName 
  90. reason: 
  91. [NSString stringWithFormat: 
  92. NSLocalizedString(@"Signal %d was raised.\n" 
  93.                                           @"%@", nil), 
  94. signal, getAppInfo()] 
  95. userInfo: 
  96. [NSDictionary 
  97. dictionaryWithObject:[NSNumber numberWithInt:signal] 
  98. forKey:UncaughtExceptionHandlerSignalKey]] 
  99. waitUntilDone:YES]; 
  100. void InstallUncaughtExceptionHandler() 
  101. signal(SIGABRT, MySignalHandler); 
  102. signal(SIGILL, MySignalHandler); 
  103. signal(SIGSEGV, MySignalHandler); 
  104. signal(SIGFPE, MySignalHandler); 
  105. signal(SIGBUS, MySignalHandler); 
  106. signal(SIGPIPE, MySignalHandler); 

在应用自身的 didFinishLaunchingWithOptions 前,加入一个函数:

  1. - (void)installUncaughtExceptionHandler 
  2. InstallUncaughtExceptionHandler(); 

最后,在 didFinishLaunchingWithOptions 中加入这一句代码就行了:

 

  1. [self InstallUncaughtExceptionHandler]; 

现在,基本上所有崩溃都能Hold住了。崩溃时将会显示出如下的对话框:

这样在崩溃时还能从容地弹出对话框,比起闪退来,用户也不会觉得那么不爽。然后在下次启动时还可以通过邮件来发送Crash文件到邮箱,这就看各个应用的需求了。

责任编辑:闫佳明 来源: cocoachina
相关推荐

2015-04-08 09:26:21

IT管理云计算基础设施数据存储

2020-07-08 08:22:08

FlutterSVGPNG

2019-08-28 07:28:13

React应用程序代码

2020-11-06 08:13:03

服务器Nodejs客户端

2021-01-18 13:17:04

鸿蒙HarmonyOSAPP

2009-11-20 11:52:10

2021-01-28 14:53:19

PHP编码开发

2015-12-03 14:33:35

2021-10-28 06:17:46

架构设计组件

2018-06-20 11:00:06

云应用开发PaaS

2016-02-29 10:01:59

iosbug合理

2017-11-13 06:35:47

混合云应用程序DevOps

2022-07-11 14:53:37

微服务容器IT

2022-05-11 10:58:11

MetricKitiOS13系统崩溃诊断

2015-02-26 09:19:00

2022-07-13 13:29:56

微服务容器开发

2014-09-22 15:14:04

2015-06-01 10:48:00

虚拟机云计算云就绪

2021-04-20 15:38:56

iOS平台应用分发开发者

2020-03-24 14:23:08

iOS 14查找苹果
点赞
收藏

51CTO技术栈公众号