iOS разработка: Crash исключения, обобщение и обработка
Когда количество пользователей приложения достигает определённого уровня, пользователи в процессе использования приложения могут столкнуться с ситуациями, которые приводят к аварийному завершению работы программы. Эти ситуации необходимо собирать.
Обычно для анализа статистики аварий приложений используют сторонние платформы, такие как:
В этом блоге рассказывается, как использовать собственный SDK Apple NSException для сбора информации об аварийных завершениях работы.
Говоря об обработке исключений, нельзя не упомянуть о проблеме сбоев (Crash) в iOS. В iOS сбои можно разделить на две категории:
К сожалению, мы можем только фиксировать вторую категорию, но если мы правильно ведём журнал, то сможем решить большинство проблем с аварийными завершениями работы в приложении. Ниже мы рассмотрим обработку второй категории.
Полный код можно скачать по адресу: открытый китайский Demo.
Часть 1: Системные сбои
Системные сбои, приводящие к аварийному завершению программы, можно обработать с помощью механизма NSSetUncaughtExceptionHandler. Этот метод довольно прост.
Часть 2: Обработка сигналов
Использование обработки исключений в Obj-C не позволяет обрабатывать сигналы. Если мы хотим их обработать, нам также нужно использовать стандартный механизм сигналов Unix для регистрации функций обработки сигналов SIGABRT, SIGBUS, SIGSEGV и других. В этой функции мы можем выводить информацию о стеке, версию и всё, что захотим. NSSetUncaughtExceptionHandler используется для обработки исключений, но его функциональность очень ограничена. Большинство причин аварий, таких как ошибки доступа к памяти и повторное освобождение, не могут быть обработаны этим методом, поскольку эти ошибки вызывают сигналы, и для их обработки требуется специальный механизм обработки сигналов.
После изучения множества блогов и интеграции различных решений я создал класс UncaughtExceptionHandler для сбора и обработки всей информации о сбоях. Метод выглядит следующим образом:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface UncaughtExceptionHandler : NSObject
/*!
* Метод обработки исключений
*
* @param install Разрешить ли сбор исключений
* @param showAlert Отображать ли alertView при возникновении исключения
*/
+ (void)installUncaughtExceptionHandler:(BOOL)install showAlert:(BOOL)showAlert;
@end
#import "UncaughtExceptionHandler.h"
#include <libkern/OSAtomic.h>
#include <execinfo.h>
//NSException имя ошибки
NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
//signal ошибка стека
NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
//ошибка стека информации
NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";
//инициализация количества ошибок
volatile int32_t UncaughtExceptionCount = 0;
//максимальное количество ошибок
const int32_t UncaughtExceptionMaximum = 10;
///отображать ли alertView
static BOOL showAlertView = nil;
//обработка исключений
void HandleException(NSException *exception);
//обработчик сигнала типа ошибки
void SignalHandler(int signal);
//получить информацию о приложении
NSString* getAppInfo(void);
@interface UncaughtExceptionHandler()
///продолжать ли выполнение программы
@property (assign, nonatomic) BOOL dismissed;
@end
@implementation UncaughtExceptionHandler
/*!
* 1、Обработка исключений
*
* @param install Разрешить ли сбор исключений
* @param showAlert Отображать ли alertView при возникновении исключения
*/
+ (void)installUncaughtExceptionHandler:(BOOL)install showAlert:(BOOL)showAlert {
if (install && showAlert) {
[[self alloc] alertView:showAlert];
}
NSSetUncaughtExceptionHandler(install ? HandleException : NULL);
signal(SIGABRT, install ? SignalHandler : SIG_DFL);
signal(SIGILL, install ? SignalHandler : SIG_DFL);
signal(SIGSEGV, install ? SignalHandler : SIG_DFL);
signal(SIGFPE, install ? SignalHandler : SIG_DFL);
signal(SIGBUS, install ? SignalHandler : SIG_DFL);
signal(SIGPIPE, install ? SignalHandler : SIG_DFL);
}
///Установить, отображать ли alertView
- (void)alertView:(BOOL)show {
showAlertView = show;
}
``` Данный фрагмент написан на языке Objective-C.
cancelButtonTitle:@"退出"
otherButtonTitles:@"继续", nil];
[alert show];
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!self.dismissed) {
//点击继续
for (NSString *mode in (__bridge NSArray *)allModes) {
//快速切换Mode
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];
}
//点击退出
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex {
#pragma clang diagnostic pop
if (anIndex == 0) {
self.dismissed = YES;
}
}
//验证和保存错误数据
- (void)validateAndSaveCriticalApplicationData:(NSException *)exception {
NSString *exceptionInfo = [NSString stringWithFormat:@"\n--------Log Exception---------\nappInfo :\n%@\n\nexception name :%@\nexception reason :%@\nexception userInfo :%@\ncallStackSymbols :%@\n\n--------End Log Exception-----", getAppInfo(),exception.name, exception.reason, exception.userInfo ? : @"no user info", [exception callStackSymbols]];
NSLog(@"%@", exceptionInfo);
///写入文件
//[exceptionInfo writeToFile:[NSString stringWithFormat:@"%@/Documents/error.log",NSHomeDirectory()] atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
@end
///2.1、奔溃异常处理
void HandleException(NSException *exception) {
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
// 如果太多不用处理
if (exceptionCount > UncaughtExceptionMaximum) {
return;
}
//获取调用堆栈
NSArray *callStack = [exception callStackSymbols];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
//在主线程中,执行制定的方法, withObject是执行方法传入的参数
[[[UncaughtExceptionHandler alloc] init]
performSelectorOnMainThread:@selector(handleException:)
withObject:
[NSException exceptionWithName:[exception name]
reason:[exception reason]
userInfo:userInfo]
waitUntilDone:YES];
}
//2.2、signal报错处理
void SignalHandler(int signal) {
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
// 如果太多不用处理
if (exceptionCount > UncaughtExceptionMaximum) {
return;
}
NSString* description = nil;
switch (signal) {
case SIGABRT:
description = [NSString stringWithFormat:@"Signal SIGABRT was raised!\n"];
break;
case SIGILL:
description = [NSString stringWithFormat:@"Signal SIGILL was raised!\n"];
break;
case SIGSEGV:
description = [NSString stringWithFormat:@"Signal SIGSEGV was raised!\n"];
break;
case SIGFPE:
description = [NSString stringWithFormat:@"Signal SIGFPE was raised!\n"];
break;
case SIGBUS:
description = [NSString stringWithFormat:@"Signal SIGBUS was raised!\n"];
break;
case SIGPIPE:
description = [NSString stringWithFormat:@"Signal SIGPIPE was raised!\n"];
break;
default:
description = [NSString stringWithFormat:@"Signal %d was raised!",signal];
}
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
NSArray *callStack = [UncaughtExceptionHandler backtrace];
[userInfo setObject:callStack];
} **forKey:UncaughtExceptionHandlerAddressesKey];**
**[userInfo setObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey];**
//在主线程中,执行指定的方法, withObject是执行方法传入的参数
**[[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:) withObject:**
**[NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName reason: description userInfo: userInfo] waitUntilDone:YES];**}
///获取app信息
**NSString* getAppInfo() {**
**NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@)\nDevice : %@\nOS Version : %@ %@\n",**
**[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"],**
**[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],**
**[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],**
**[UIDevice currentDevice].model,**
**[UIDevice currentDevice].systemName,**
**[UIDevice currentDevice].systemVersion];**
**return appInfo;**}
### 三、测试
**以下是我的三个测试案例,前两个方法属于系统奔溃,直接编译运行,即可监听到Crash.**
```oc
#import "UncaughtExceptionHandler.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//捕获异常
**[UncaughtExceptionHandler installUncaughtExceptionHandler:YES showAlert:YES];**
**return YES;**}
Ниже приведены три тестовых примера для проверки работы обработчика исключений. Первые два примера вызывают системные сбои, которые можно отследить при непосредственной компиляции и запуске кода. Третий пример относится к классу сигналов SIGABRT, который может возникнуть в результате различных ошибок, таких как доступ к несуществующему методу или выход за границы массива.
В Xcode программа не входит в функцию bugrpt_signalHandler при возникновении сигнала SIGABRT. Это связано с тем, что Xcode скрывает обработку сигналов. Чтобы войти в эту функцию, необходимо выполнить команду в lldb: pro hand -p true -s false SIGABRT. Здесь SIGABRT можно заменить на любой другой сигнал, например, SIGSEGV.
После выполнения программы выводится сообщение об ошибке:
set a breakpoint in malloc_error_break to debug
2018-09-07 13:04:58.097353 DemoExceptionHandler[266:16655]
--------Log Exception---------
appInfo :
App : (null) 1.0(1)
Device : iPhone
OS Version : iOS 10.2
exception name :UncaughtExceptionHandlerSignalExceptionName
exception reason :Signal SIGABRT was raised!
exception userInfo :{
UncaughtExceptionHandlerAddressesKey = (
"0 DemoExceptionHandler 0x00000001000253dc +[UncaughtExceptionHandler backtrace] + 76",
"1 DemoExceptionHandler 0x0000000100025174 SignalHandler + 676",
"2 libsystem_platform.dylib 0x0000000182129338 _sigtramp + 36",
"3 libsystem_pthread.dylib 0x000000018212f450 pthread_kill + 112",
"4 libsystem_c.dylib 0x0000000181fdb400 abort + 140",
"5 libsystem_malloc.dylib 0x000000018209d944 + 0",
"6 DemoExceptionHandler 0x0000000100024a50 -[ViewController exceptionHandlerTest3] + 96",
"7 DemoExceptionHandler 0x00000001000248a4 -[ViewController viewDidLoad] + 92",
"8 UIKit 0x0000000188f4e924 + 1056",
"9 UIKit"
} Текст запроса:
0x0000000188f4e4ec + 28", "10 UIKit 0x0000000188f54c98 + 76", "11 UIKit 0x0000000188f52138 + 272", "12 UIKit 0x0000000188fc468c + 48", "13 UIKit 0x00000001891d0cb8 + 4068", "14 UIKit 0x00000001891d6808 + 1656", "15 UIKit 0x00000001891eb104 + 48", "16 UIKit 0x00000001891d37ec + 168", "17 FrontBoardServices 0x0000000184c6f92c + 36", "18 FrontBoardServices 0x0000000184c6f798 + 176", "19 FrontBoardServices 0x0000000184c6fb40 + 56", "20 CoreFoundation 0x0000000183046b5c + 24", "21 CoreFoundation 0x00000001830464a4 + 524", "22 CoreFoundation 0x00000001830440a4 + 804", "23 CoreFoundation 0x0000000182f722b8 CFRunLoopRunSpecific + 444", "24 UIKit 0x0000000188fb97b0 + 608", "25 UIKit 0x0000000188fb4534 UIApplicationMain + 208", "26 DemoExceptionHandler 0x0000000100025fec main + 124", "27 libdyld.dylib 0x0000000181f555b8 + 4" ); UncaughtExceptionHandlerSignalKey = 6; } callStackSymbols :(null)
--------End Log Exception----- 2018-09-07 13:04:58.280259 DemoExceptionHandler[266:16655] invalid mode 'kCFRunLoopCommonModes' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debug. This message will only appear once per execution.
Перевод текста на русский язык:
В запросе представлен текст, который содержит информацию о стеке вызовов в программе. В тексте также есть сообщение об ошибке, связанной с режимом работы цикла выполнения программы.
Текст не содержит информации, которая требует профессионального перевода или технического редактирования. СИГУРГ | возникает при получении «срочных» данных или внеполосных данных через сокет.
24. SIGXCPU | превышение ограничения ресурсов времени процессора. Это ограничение можно прочитать или изменить с помощью getrlimit/setrlimit.
25. SIGXFSZ | попытка процесса расширить файл до размера, превышающего ограничение на размер файла.
26. SIGVTALRM | сигнал виртуального таймера. Аналогичен SIGALRM, но измеряет время использования процессора данным процессом.
27. SIGPROF | аналогично SIGALRM и SIGVTALRM, но включает в себя как время процессора, так и время системных вызовов процесса.
28. SIGWINCH | отправляется при изменении размера окна.
29. SIGIO | дескриптор файла готов, можно начать операции ввода/вывода.
30. SIGPWR | сбой питания.
31. SIGSYS | незаконный системный вызов.
Чтобы запросить таблицу сигналов на локальном компьютере, введите «kill -l» в терминале, как показано на рисунке ниже:
Его конкретный список LinuxSignal можно найти по ссылке: список сигналов Linux.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )