iOS Crash捕獲
iOS端的crash分為兩類肴楷,一類是NSException異常年鸳,另外一類是Signal信號(hào)異常屠升。這兩類異常我們都可以通過注冊相關(guān)函數(shù)來捕獲贱除。
1、對于unrecognized selector sent to instance找不到方法iOS系統(tǒng)拋出異常崩潰规脸,給NSObject添加一個(gè)分類,實(shí)現(xiàn)消息轉(zhuǎn)發(fā)的幾個(gè)方法
利用runtime消息轉(zhuǎn)發(fā)處理未實(shí)現(xiàn)的方法crash盖溺,代碼如下:
#import <objc/runtime.h>
static NSString *_errorFunctionName;
void dynamicMethodIMP(id self,SEL _cmd) {}
static inline void change_method(Class _originalClass ,SEL _originalSel,Class _newClass ,SEL _newSel) {
Method methodOriginal = class_getInstanceMethod(_originalClass, _originalSel);
Method methodNew = class_getInstanceMethod(_newClass, _newSel);
method_exchangeImplementations(methodOriginal, methodNew);
}
@implementation NSObject(UnRecognized)
+ (void)load {
change_method([self class], @selector(methodSignatureForSelector:), [self class], @selector(ws_methodSignatureForSelector:));
change_method([self class], @selector(forwardInvocation:), [self class], @selector(ws_forwardInvocation:));
}
- (NSMethodSignature *)ws_methodSignatureForSelector:(SEL)aSelector {
if (![self respondsToSelector:aSelector]) {
_errorFunctionName = NSStringFromSelector(aSelector);
NSMethodSignature *methodSignature = [self ws_methodSignatureForSelector:aSelector];
if (class_addMethod([self class], aSelector, (IMP)dynamicMethodIMP, method_getTypeEncoding(class_getInstanceMethod([self class], @selector(methodSignatureForSelector:))))) {
NSLog(@"添加臨時(shí)方法成功!");
}
if (!methodSignature) {
methodSignature = [self ws_methodSignatureForSelector:aSelector];
}
return methodSignature;
}else{
return [self ws_methodSignatureForSelector:aSelector];
}
}
- (void)ws_forwardInvocation:(NSInvocation *)anInvocation{
SEL selector = [anInvocation selector];
if ([self respondsToSelector:selector]) {
[anInvocation invokeWithTarget:self];
}else{
[self ws_forwardInvocation:anInvocation];
}
}
@end
2铣缠、對于signal產(chǎn)生的crash烘嘱,創(chuàng)建一個(gè)crash捕獲的單例,里面進(jìn)行信號(hào)捕捉處理蝗蛙,只需要在AppDelegate中進(jìn)行初始化
#import "WSCrashHandler.h"
#include <libkern/OSAtomic.h>
#include <execinfo.h>
#include <sys/signal.h>
NSString * const kSignalExceptionName = @"kSignalExceptionName";
NSString * const kSignalKey = @"kSignalKey";
NSString * const kCaughtExceptionStackInfoKey = @"kCaughtExceptionStackInfoKey";
static void HandleException(NSException *exception);
static void SignalHandler(int signal);
@interface WSCrashHandler ()
@property (nonatomic, assign) BOOL dismissed;
@end
@implementation WSCrashHandler
+ (instancetype)shared {
static WSCrashHandler *sharedHelper = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
sharedHelper = [[WSCrashHandler alloc] init];
});
return sharedHelper;
}
- (instancetype)init {
self = [super init];
if (self) {
// 1.捕獲一些異常導(dǎo)致的崩潰
NSSetUncaughtExceptionHandler(&HandleException);
// 2.捕獲非異常情況拙友,通過signal傳遞出來的崩潰
//signal是一個(gè)函數(shù),有2個(gè)參數(shù)歼郭,第一個(gè)是int類型遗契,第二個(gè)參數(shù)是一個(gè)函數(shù)指針
//添加想要監(jiān)聽的signal類型,當(dāng)發(fā)出相應(yīng)類型的signal時(shí)病曾,會(huì)回調(diào)SignalHandler方法
signal(SIGABRT, SignalHandler);// SIGABRT--程序中止命令中止信號(hào)
signal(SIGILL, SignalHandler);//SIGILL--程序非法指令信號(hào)
signal(SIGSEGV, SignalHandler);//SIGSEGV--程序無效內(nèi)存中止信號(hào)
signal(SIGFPE, SignalHandler);//SIGFPE--程序浮點(diǎn)異常信號(hào)
signal(SIGBUS, SignalHandler);//SIGBUS--程序內(nèi)存字節(jié)未對齊中止信號(hào)
signal(SIGPIPE, SignalHandler);//SIGPIPE--程序Socket發(fā)送失敗中止信號(hào)
}
return self;
}
//NSException異常是OC代碼導(dǎo)致的crash
void HandleException(NSException *exception) {
NSString *message = [NSString stringWithFormat:@"崩潰原因如下:\n%@\n%@",
[exception reason],
[[exception userInfo] objectForKey:kCaughtExceptionStackInfoKey]];
NSLog(@"%@",message);
// 獲取NSException異常的堆棧信息
NSArray *callStack = [exception callStackSymbols];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo setObject:callStack forKey:kCaughtExceptionStackInfoKey];
WSCrashHandler *crashObject = [WSCrashHandler shared];
NSException *customException = [NSException exceptionWithName:[exception name] reason:[exception reason] userInfo:userInfo];
[crashObject performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES];
}
//signal信號(hào)拋出的異常處理
void SignalHandler(int signal) {
NSArray *callStack = [WSCrashHandler backtrace];
NSLog(@"signal信號(hào)捕獲崩潰牍蜂,堆棧信息:%@",callStack);
WSCrashHandler *crashObject = [WSCrashHandler shared];
NSException *customException = [NSException exceptionWithName:kSignalExceptionName
reason:[NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.", nil),signal]
userInfo:@{kSignalKey:[NSNumber numberWithInt:signal]}];
[crashObject performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES];
}
- (void)handleException:(NSException *)exception {
// 打印或彈出框
// TODO :
#ifdef DEBUG
NSString *message = [NSString stringWithFormat:@"抱歉,APP發(fā)生了異常泰涂,點(diǎn)擊屏幕繼續(xù)并自動(dòng)復(fù)制錯(cuò)誤信息到剪切板鲫竞。\n\n異常報(bào)告:\n異常名稱:%@\n異常原因:%@\n", [exception name], [exception reason]];
NSLog(@"%@",message);
[self showCrashToastWithMessage:message];
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!self.dismissed) {
for (NSString *mode in (__bridge NSArray *)allModes) {
//為阻止線程退出,使用 CFRunLoopRunInMode(model, 0.001, false)等待系統(tǒng)消息逼蒙,false表示RunLoop沒有超時(shí)時(shí)間
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
CFRelease(allModes);
#endif
// 本地保存exception異常信息并上傳服務(wù)器
// TODO :
// 下面等同于清空之前設(shè)置的
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:kSignalExceptionName]) {
kill(getpid(), [[[exception userInfo] objectForKey:kSignalKey] intValue]);
} else {
[exception raise];
}
}
//該函數(shù)用來獲取當(dāng)前線程調(diào)用堆棧的信息从绘,并且轉(zhuǎn)化為字符串?dāng)?shù)組。
+ (NSArray *)backtrace {
void *callStack[128];//堆棧方法數(shù)組
int frames = backtrace(callStack, 128);//獲取錯(cuò)誤堆棧方法指針數(shù)組是牢,返回?cái)?shù)目
char **strs = backtrace_symbols(callStack, frames);//符號(hào)化
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames]; //函數(shù)調(diào)用信息依照順序存在NSMutableArray backtrace
for (int i = 0; i < frames; i++) {
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
- (void)showCrashToastWithMessage:(NSString *)message {
UILabel *crashLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 64, [UIApplication sharedApplication].keyWindow.bounds.size.width, [UIApplication sharedApplication].keyWindow.bounds.size.height - 64)];
crashLabel.textColor = [UIColor redColor];
crashLabel.font = [UIFont systemFontOfSize:15];
crashLabel.text = message;
crashLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.7];
crashLabel.numberOfLines = 0;
[[UIApplication sharedApplication].keyWindow addSubview:crashLabel];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(crashToastTapAction:)];
crashLabel.userInteractionEnabled = YES;
[crashLabel addGestureRecognizer:tap];
}
- (void)crashToastTapAction:(UITapGestureRecognizer *)tap {
UILabel *crashLabel = (UILabel *)tap.view;
[UIPasteboard generalPasteboard].string = crashLabel.text;
self.dismissed = YES;
}
@end
3僵井、實(shí)例測試:
關(guān)鍵點(diǎn):SignalHandler不要在debug環(huán)境下測試。因?yàn)橄到y(tǒng)的debug會(huì)優(yōu)先去攔截驳棱。先在run的切換Release狀態(tài),運(yùn)行安裝后批什,再手動(dòng)點(diǎn)擊啟動(dòng)app去測試。
實(shí)例測試代碼如下:
#import "ViewController.h"
@interface ViewController ()
@end
typedef struct Test {
int a;
int b;
}Test;
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
// Do any additional setup after loading the view.
UIButton *crashExcButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 44, self.view.bounds.size.width, 50)];
crashExcButton.backgroundColor = [UIColor redColor];
[crashExcButton setTitle:@"Exception" forState:UIControlStateNormal];
[crashExcButton addTarget:self action:@selector(crashExcClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:crashExcButton];
UIButton *crashSignalEGVButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 60+44, self.view.bounds.size.width, 50)];
crashSignalEGVButton.backgroundColor = [UIColor redColor];
[crashSignalEGVButton setTitle:@"Signal(EGV)" forState:UIControlStateNormal];
[crashSignalEGVButton addTarget:self action:@selector(crashSignalEGVClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:crashSignalEGVButton];
UIButton *crashSignalBRTButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 120+44, self.view.bounds.size.width, 50)];
crashSignalBRTButton.backgroundColor = [UIColor redColor];
[crashSignalBRTButton setTitle:@"Signal(ABRT)" forState:UIControlStateNormal];
[crashSignalBRTButton addTarget:self action:@selector(crashSignalBRTClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:crashSignalBRTButton];
UIButton *crashSignalBUSButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 180+44, self.view.bounds.size.width, 50)];
crashSignalBUSButton.backgroundColor = [UIColor redColor];
[crashSignalBUSButton setTitle:@"Signal(BUS)" forState:UIControlStateNormal];
[crashSignalBUSButton addTarget:self action:@selector(crashSignalBUSClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:crashSignalBUSButton];
}
- (void)crashSignalEGVClick {
UIView *view = [[UIView alloc] init];
[view performSelector:NSSelectorFromString(@"release")];//導(dǎo)致SIGSEGV的錯(cuò)誤社搅,一般會(huì)導(dǎo)致進(jìn)程流產(chǎn)
view.backgroundColor = [UIColor whiteColor];
}
- (void)crashSignalBRTClick {
Test *pTest = {1,2};
free(pTest);//導(dǎo)致SIGABRT的錯(cuò)誤驻债,因?yàn)閮?nèi)存中根本就沒有這個(gè)空間,哪來的free形葬,就在棧中的對象而已
pTest->a = 5;
}
- (void)crashSignalBUSClick {
//SIGBUS合呐,內(nèi)存地址未對齊
//EXC_BAD_ACCESS(code=1,address=0x1000dba58)
char *s = "hello world";
*s = 'H';
}
- (void)crashExcClick {
[self performSelector:@selector(aaaa)];
}
@end