首先我們要知道crash 的類型有哪些
1.數(shù)組越界導(dǎo)致的崩潰
2.數(shù)據(jù)集合類型,如字典、數(shù)組中插入元素時,插入空指針nil
3.調(diào)用當(dāng)前對象類中不存在的方法導(dǎo)致崩潰
4.調(diào)用的庫函數(shù)版本高于本機(jī)
5.內(nèi)存管理不當(dāng),向野指針發(fā)送消息導(dǎo)致的崩潰(包括某些信號量奔潰)
6.EXC_ARITHMETIC 除零操作
那么一般情況下,我們可以通過獲取信號量
崩潰的信息,來判斷崩潰的情況
信號量的崩潰
系統(tǒng)為我們提供了獲取信號量信息的方法
需要導(dǎo)入兩個頭文件
#include <libkern/OSAtomic.h>
#include <execinfo.h>
我們可以寫一個類方法來設(shè)置
+ (void)RegisterSignalHandler{
//注冊程序由于abort()函數(shù)調(diào)用發(fā)生的程序中止信號
signal(SIGABRT, HandleException);
//注冊程序由于非法指令產(chǎn)生的程序中止信號
signal(SIGILL, HandleException);
//注冊程序由于無效內(nèi)存的引用導(dǎo)致的程序中止信號
signal(SIGSEGV, HandleException);
//注冊程序由于浮點數(shù)異常導(dǎo)致的程序中止信號
signal(SIGFPE, HandleException);
//注冊程序由于內(nèi)存地址未對齊導(dǎo)致的程序中止信號
signal(SIGBUS, HandleException);
//程序通過端口發(fā)送消息失敗導(dǎo)致的程序中止信號
signal(SIGPIPE, HandleException);
}
在每種對應(yīng)的信息下,我們可以寫一個方法去捕獲異常信息
//捕獲信號后的回調(diào)函數(shù)
//當(dāng)前處理的異常個數(shù)
volatile int32_t UncaughtExceptionCount = 0;
//最大能夠處理的異常個數(shù)
volatile int32_t UncaughtExceptionMaximum = 10;
void HandleException(int signo)
{
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum)
{
return;
}
NSMutableDictionary *userInfo =[NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signo] forKey:@"signal"];
//創(chuàng)建一個OC異常對象
NSException *ex = [NSException exceptionWithName:@"SignalExceptionName" reason:[NSString stringWithFormat:@"Signal %d was raised.\n",signo] userInfo:userInfo];
//處理異常消息
[[SignalHandler Instance] performSelectorOnMainThread:@selector(HandleException:) withObject:ex waitUntilDone:YES];
}
那么在這個處理異常信息的時候我們用到的是單例來進(jìn)行管理的
static SignalHandler *s_SignalHandler = nil;
+ (instancetype)Instance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (s_SignalHandler == nil) {
s_SignalHandler = [[SignalHandler alloc] init];
}
});
return s_SignalHandler;
}
接下來便是處理異常信息的方法,可以保存在本機(jī),可以上傳至服務(wù)端保存
BOOL isDismissed = NO;
//處理異常用到的方法
- (void)HandleException:(NSException *)exception
{
NSLog(@"CRASH: %@", exception);
NSLog(@"Stack Trace: %@", [exception callStackSymbols]);
//創(chuàng)建沙盒保存路徑
NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *errordir=[documentDirectory stringByAppendingPathComponent:@"errordump"];
BOOL isdir;
NSFileManager *fm=[NSFileManager defaultManager];
if(![fm fileExistsAtPath:errordir isDirectory:&isdir]){
[fm createDirectoryAtPath:errordir withIntermediateDirectories:YES attributes:nil error:nil];
}
NSString *str=[NSString stringWithFormat:@"%@:%@",exception,
[exception callStackSymbols]];
//生成crash信息文本
NSString *errorfile=[errordir stringByAppendingPathComponent:[NSString stringWithFormat:@"%@signal_error.txt",[NSDate date]]];
[str writeToFile:errorfile atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"errorFile:%@",errorfile);
//這里要獲取runloop
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
//當(dāng)接收到異常處理消息時,讓程序開始runloop,防止程序死亡
while (!isDismissed) {
for (NSString *mode in (NSArray *)CFBridgingRelease(allModes))
{
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
//可以通過彈窗的方式告知用戶
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"程序出現(xiàn)問題啦" message:@"崩潰信息" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:nil];
[alertView show];
//當(dāng)點擊彈出視圖的Cancel按鈕,isDimissed = YES,上邊的循環(huán)跳出
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);
}
- (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex
{
//因為這個彈出視圖只有一個Cancel按鈕,所以直接進(jìn)行修改isDimsmissed這個變量了
isDismissed = YES;
}