1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/chenzm_186-DemoExceptionHandler

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

iOS разработка: Crash исключения, обобщение и обработка

Когда количество пользователей приложения достигает определённого уровня, пользователи в процессе использования приложения могут столкнуться с ситуациями, которые приводят к аварийному завершению работы программы. Эти ситуации необходимо собирать.

Обычно для анализа статистики аварий приложений используют сторонние платформы, такие как:

    1. Юмэн (友盟).
    1. Flurry.
    1. Crashlytics.

В этом блоге рассказывается, как использовать собственный SDK Apple NSException для сбора информации об аварийных завершениях работы.

Говоря об обработке исключений, нельзя не упомянуть о проблеме сбоев (Crash) в iOS. В iOS сбои можно разделить на две категории:

  1. Сбои, вызванные EXC_BAD_ACCESS, что обычно происходит из-за доступа к памяти, которая не принадлежит текущему процессу, или возможно, к уже освобождённой памяти.
  2. Необработанные Objective-C исключения (NSException), которые записываются и приводят к тому, что программа отправляет себе сигнал SIGABRT и аварийно завершается.

К сожалению, мы можем только фиксировать вторую категорию, но если мы правильно ведём журнал, то сможем решить большинство проблем с аварийными завершениями работы в приложении. Ниже мы рассмотрим обработку второй категории.

Полный код можно скачать по адресу: открытый китайский 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 | незаконный системный вызов.

  • В перечисленных сигналах:
    1. Неперехватываемые, блокируемые или игнорируемые сигналы: SIGKILL, SIGSTOP.
    2. Сигналы, которые не могут быть восстановлены до действия по умолчанию: SIGILL, SIGTRAP.
    3. Сигналы, приводящие к завершению процесса по умолчанию: SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGIOT, SIGQUIT, SIGSEGV, SIGTRAP, SIGXCPU, SIGXFSZ.
    4. Сигналы, приводящие к выходу из процесса по умолчанию: SIGALRM, SIGHUP, SIGINT, SIGKILL, SIGPIPE, SIGPOLL, SIGPROF, SIGSYS, SIGTERM, SIGUSR1, SIGUSR2, SIGVTALRM.
    5. Сигналы, останавливающие процесс по умолчанию: SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU.
    6. Игнорируемые сигналы по умолчанию: SIGCHLD, SIGPWR, SIGURG, SIGWINCH.
    7. Кроме того, SIGIO в SVR4 приводит к выходу, а в 4.3BSD — к игнорированию;
    8. SIGCONT при приостановке процесса продолжается, в противном случае игнорируется, не может быть заблокирован.

Чтобы запросить таблицу сигналов на локальном компьютере, введите «kill -l» в терминале, как показано на рисунке ниже:

Рисунок три

Его конкретный список LinuxSignal можно найти по ссылке: список сигналов Linux.

Пять. Анализ Crash Callstack, описание свойств:

  1. 0x8badf00d: указывает на то, что приложение запустилось, завершилось или отреагировало на системное событие слишком долго, что означает «ate bad food».
  2. 0xdeadfa11: пользователь принудительно вышел, что означает «dead fall». (Пользователь нажимает кнопку питания и HOME, когда система не отвечает).
  3. 0xbaaaaaad: пользователь нажал Home и Volume, чтобы получить текущее состояние памяти, что не представляет собой сбой.
  4. 0xbad22222: VoIP-приложение часто восстанавливается, вызывая сбой.
  5. 0xc00010ff: слишком жарко, поэтому его отключают, что означает «cool off».
  6. 0xdead10cc: потому что он занимает системные ресурсы (например, адресную книгу) в фоновом режиме, его отключают, что означает «dead lock».
Ссылки на статьи:
  1. Обработка функций обратного вызова сигнала в Xcode;
  2. Исключения iOS.

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/chenzm_186-DemoExceptionHandler.git
git@api.gitlife.ru:oschina-mirror/chenzm_186-DemoExceptionHandler.git
oschina-mirror
chenzm_186-DemoExceptionHandler
chenzm_186-DemoExceptionHandler
master