iOS崩潰 捕獲異常處理
網上基本使用的都是同一個版本的異常捕獲,我能瞭解到的關於signal異常捕獲的方法也是通過這個版本。我在自己理解的基礎上對於這個版本進行了一些修改,也添加了一些註釋。下面貼出主要的程式碼。
完整程式碼的下載地址:git
/*!
* 異常的處理方法
*
* @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);
}
產生上述的signal的時候就會呼叫我們定義的SignalHandler
來處理異常,而NSSetUncaughtExceptionHandler
就是iOS SDK中提供的一個現成的函式,用來捕獲異常的方法,使用方便。但它不能捕獲丟擲的signal,所以定義了SignalHandler
方法。
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];
}
這部分的方法就是對應NSSetUncaughtExceptionHandler
的處理,只要方法關聯到這個函式,那麼發生相應錯誤時會自動呼叫該函式,呼叫時會傳入exception
引數。獲取異常後會將捕獲的異常傳入最終呼叫處理的handleException
函式,後面會提到。
//處理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];
}
上面就是用來處理NSSetUncaughtExceptionHandler
無法捕獲的signal
。由於signal
傳入時都是int
值,所以我把比較常見的signal
對應的巨集定義在這裡描述出來,方便記錄和閱讀。和上面一樣,這裡最後也是會將捕獲的異常傳入handleException
函式。可以注意到這裡獲取呼叫堆疊的方法和上面不同,上面的函式在系統呼叫時就會傳入exception
,通過exception
可以很方便的獲取到呼叫堆疊,但是這裡不一樣,系統呼叫時傳入的僅僅是signal
值。
//獲取呼叫堆疊
+ (NSArray *)backtrace {
//指標列表
void* callstack[128];
//backtrace用來獲取當前執行緒的呼叫堆疊,獲取的資訊存放在這裡的callstack中
//128用來指定當前的buffer中可以儲存多少個void*元素
//返回值是實際獲取的指標個數
int frames = backtrace(callstack, 128);
//backtrace_symbols將從backtrace函式獲取的資訊轉化為一個字串陣列
//返回一個指向字串陣列的指標
//每個字串包含了一個相對於callstack中對應元素的可列印資訊,包括函式名、偏移地址、實際返回地址
char **strs = backtrace_symbols(callstack, frames);
int i;
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (i = 0; i < frames; i ++) {
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
上面就是我們自己獲取到呼叫堆疊的方法。backtrace是Linux下用來追蹤函式呼叫堆疊以及定位段錯誤的函式。
- (void)handleException:(NSException *)exception {
[self validateAndSaveCriticalApplicationData:exception];
//不顯示alertView就不執行下面的程式碼
if (!showAlertView) {
return;
}
//alertView
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
UIAlertView *alert =
[[UIAlertView alloc]
initWithTitle:@"出錯啦"
message:[NSString stringWithFormat:@"你可以嘗試繼續操作,但是應用可能無法正常執行.\n"]
delegate:self
cancelButtonTitle:@"退出"
otherButtonTitles:@"繼續", nil];
[alert show];
#pragma clang diagnostic pop
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!self.dismissed) {
//點選繼續
for (NSString *mode in (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];
}
}
上面的函式就是最紅用來處理異常的函式,在validateAndSaveCriticalApplicationData
函式裡我們可以根據自己的需求進行操作,比如可以把異常資訊寫入本地在特定的時間傳送給指定伺服器,或者實時的進行資訊的傳送等。這裡遮蔽了一些警告,因為專案要支援到iOS7。
使用
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after app launch
[UncaughtExceptionHandler installUncaughtExceptionHandler:YES showAlert:YES];
return YES;
}
寫在最後
其實這上面的程式碼我也不是完全弄懂了每一句話,比如OSAtomicIncrement32(&UncaughtExceptionCount)
,比如alertView點選了退出按鈕後執行的一系列程式碼。如果有知道的留言告訴我一下吧~~小女子不勝感激!