iOS崩潰異常捕獲

? ? ? ? ? ? 最近項目上需要對崩潰信息進行處理,要滿足崩潰后及時捕捉到崩潰信息,當應用下次打開后再將報文上傳至服務器。下面就來談談這些天個人的一些收獲。

? ? ? ?我們通常所接觸到的崩潰主要涉及到兩種:

? ? ? 一:由EXC_BAD_ACCESS引起的,原因是內存訪問錯誤,重復釋放等錯誤

? ? ? 二: 未被捕獲的Objective-C異常(NSException)

? ? ? 針對NSException這種錯誤,可以直接調用NSSetUncaughtExceptionHandler函數來捕獲;而針對EXC_BAD_ACCESS錯誤則需通過自定義注冊SIGNAL來捕獲。一般產生一個NSException異常的時候,同時也會拋出一個SIGNAL的信號(當然這只是一般情況,有時可能只是會單獨出現)。

/*!

*? 開啟異常的處理方法

*/

void InstallUncaughtExceptionHandler(void) {

//捕捉NSException錯誤

NSSetUncaughtExceptionHandler (&uncaughtExceptionHandler);

//以下是注冊SIGNAL信號

//4:執行了非法指令. 通常是因為可執行文件本身出現錯誤, 或者試圖執行數據段. 堆棧溢出時也有可能產生這個信號。

signal(SIGILL, mySignalHandler);

//6:調用abort函數生成的信號。

signal(SIGABRT, mySignalHandler);

//7:非法地址, 包括內存地址對齊(alignment)出錯。比如訪問一個四個字長的整數, 但其地址不是4的倍數。它與SIGSEGV的區別在于后者是由于對合法存儲地址的非法訪問觸發的(如訪問不屬于自己存儲空間或只讀存儲空間)。

signal(SIGBUS, mySignalHandler);

//8:在發生致命的算術運算錯誤時發出. 不僅包括浮點運算錯誤, 還包括溢出及除數為0等其它所有的算術的錯誤。

signal(SIGFPE, mySignalHandler);

//11:試圖訪問未分配給自己的內存, 或試圖往沒有寫權限的內存地址寫數據.

signal(SIGSEGV, mySignalHandler);

//13:管道破裂。這個信號通常在進程間通信產生,比如采用FIFO(管道)通信的兩個進程,讀管道沒打開或者意外終止就往管道寫,寫進程會收到SIGPIPE信號。此外用Socket通信的兩個進程,寫進程在寫Socket的時候,讀進程已經終止。

signal(SIGPIPE, mySignalHandler);

}

產生上述的signal的時候就會調用我們定義的mySignalHandler來處理異常,當產生NSException錯誤時就會調用系統提供的一個現成的函數NSSetUncaughtExceptionHandler(),這里面的方法名自己隨便取。

void uncaughtExceptionHandler(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 mySignalHandler(int signal) {

int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);

// 如果太多不用處理

if (exceptionCount > UncaughtExceptionMaximum) {

return;

}

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。和上面一樣,這里最后也是會將捕獲的異常傳入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];

//在這里我們可以進行延時操作,保證在崩潰前我們能有充分的時間來完成對于崩潰信息的采集

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];

}

}

//使用開啟監聽

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

// Override point for customization after app launch

InstallUncaughtExceptionHandler();

return YES;

}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 程序都會有bug,那么在有bug和異常時能不能給個提醒呢,今天咱們來看看這個問題怎么解決 首先說下基本的NSExc...
    愛吃魚的小灰閱讀 709評論 1 9
  • 文章目錄 一. 系統Crash 二. 處理signal 下面是一些信號說明 關鍵點注意 三. 實戰 四. Cras...
    MTDeveloper閱讀 1,168評論 1 2
  • 要接入上海分部的自己的性能統計和事件統計的 sdk,在接入之前自己了解下。看到了一套捕獲異常的代碼。 對于異常分為...
    南京小伙閱讀 873評論 2 1
  • 1、信號的理解 信號的概念:信號(本人關于signal的一篇博客) http://www.lxweimin.com/...
    好雨知時節浩宇閱讀 6,340評論 0 17
  • 在去往老城區廣場的路上 非常喜歡這邊建筑外面各種顏色碰撞 老城區廣場的天文鐘上端 天文鐘就在老城區廣場旁 每次整點...
    Yukinspring閱讀 261評論 0 0