iOS異常處理

盡管當iphone應(yīng)用崩潰時,它不會告訴用戶發(fā)生了什么,但我們?nèi)匀豢梢詾閼?yīng)用添加異常和信號處理,以此記錄和展示發(fā)生的變化。更進一步,我們甚至能在異常發(fā)生時,阻止應(yīng)用的崩潰。

引言

本文所用應(yīng)用將拋出指定的Object-C異常,如EXC_BAD_ACCESS異常和響應(yīng)的BSD signal。經(jīng)過處理后,所有的異常和信號都會被捕獲,然后展示調(diào)試信息,最后應(yīng)用還能繼續(xù)運行。

異常處理界面
異常處理界面

上圖應(yīng)用會在4秒后故意觸發(fā)一個未處理消息異常,并在10秒后觸發(fā)EXC_BAD_ACCESS/SIGBUS信號。

iPhone應(yīng)用崩潰原因

崩潰(準確的說是程序異常終止)是程序接收到未處理信號的結(jié)果。

未處理信號有三個來源:內(nèi)核、其他進程和應(yīng)用本身。導致崩潰最常見的兩個信號如下:

  • EXC_BAD_ACCESS是一種由內(nèi)核發(fā)出的Mach異常,通常是因為應(yīng)用試圖訪問不存在的內(nèi)存空間導致的。如果未能在Mach內(nèi)核級別進行處理,它將被轉(zhuǎn)化為SIGBUS或者SIGSEGVBSD信號。
  • SIGABRT是當產(chǎn)生未捕獲的NSException或者obj_exception_throw時,應(yīng)用發(fā)給自身的BSD信號。

在Object-C異常中,導致異常拋出最常見的原因是應(yīng)用向?qū)ο蟀l(fā)送了未實現(xiàn)的方法選擇器(比如拼寫錯誤,對象混淆或者向已經(jīng)釋放的對象發(fā)送消息)。

捕獲未捕獲異常(uncaught exceptions)

正確處理未捕獲異常的方式是在代碼中修復異常產(chǎn)生的原因。如果你的程序工作良好,那么下面講述的方法也就不重要了。

當然,有些bug也不一定總會導致應(yīng)用崩潰。此外,當你的程序中出現(xiàn)bug時,你會希望測試人員能夠返回一些有用的信息。

在這些情況下,有兩種方式可以捕獲那些會導致崩潰的未捕獲狀態(tài)。

  • 使用NSUncaughtExceptionHandler函數(shù)來安裝未捕獲Object-C異常的處理器。
  • 使用signal函數(shù)來安裝BSD信號處理器。

例如,安裝Object-C異常處理器和信號處理的代碼如下:
objc
void InstallUncaughtExceptionHandler()
{
NSSetUncaughtExceptionHandler(&HandleException);
signal(SIGABRT, SignalHandler);
signal(SIGILL, SignalHandler);
signal(SIGSEGV, SignalHandler);
signal(SIGFPE, SignalHandler);
signal(SIGBUS, SignalHandler);
signal(SIGPIPE, SignalHandler);
}
objc
對于異常和信號的響應(yīng)會在HandleExceptionSignalHandler中實現(xiàn)。在樣例程序中,以上二者的處理方式相同。

盡管本文只處理最常見的信號,但是,你可以為自己的程序添加所需的所有異常信號。

注意,有兩種異常是不能捕獲的:SIGKILLSIGSTOP。它們會終止或者暫停應(yīng)用。(SIGKILL是命令行函數(shù)kill -9發(fā)出的,SIGSTOP是鍵入Control-Z發(fā)出的)。

異常處理的要求

未處理異常處理器可能永遠不能返回

導致未處理異?;蛐盘柼幚砥鞅挥|發(fā)的場景通常都是應(yīng)用中不可逆的場景。

然而,有時僅是棧幀或者當前函數(shù)不能恢復。如果你能阻止當前棧幀繼續(xù)執(zhí)行,那么剩下的程序還可能繼續(xù)執(zhí)行。

如果你想嘗試這種情況,那么你的未處理異常處理器必須不將控制權(quán)返回給正在調(diào)用的函數(shù)——產(chǎn)生異?;蛘哂|發(fā)信號的代碼不允許被再次使用。

為了在不返回控制權(quán)給調(diào)用函數(shù)的情況下,繼續(xù)執(zhí)行代碼,我們必須返回主線程(假設(shè)我們現(xiàn)在不在主線程),并永久阻塞舊線程。同時,在主線程中,我們必須開啟新的run loop,且不在返回原來的run loop。

這意味著被導致異常的線程所使用的棧內(nèi)存將永遠泄漏。這就是代價。

嘗試恢復

由于新的run loop將用來顯示會話,它將保持無限運行,同時,它還可以取代應(yīng)用的主run loop。

為此,該run loop必須能夠處理主線程的所有模式。由于主run loop包含了許多私有模式(如GSEvent處理和滑動跟蹤),默認的NSDefaultRunLoopMode是不夠的。

幸運的是,如果UIApplication已經(jīng)創(chuàng)造了主loop的所有模式,那么,就可以從該loop中讀取所有這些模式。假設(shè)這些代碼在main loop創(chuàng)建后運行在主線程,那么它也能在所有的UIApplication模式下運行循環(huán):
objc
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);

while (!dismissed)
{
for (NSString *mode in (NSArray *)allModes)
{
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}

CFRelease(allModes);
objc

作為調(diào)試信息的一部分,我們需要獲取棧地址

你可以使用backtrace函數(shù)來取得回溯信息,并使用backtrace_symbols來將它們轉(zhuǎn)化為標記。
objc

  • (NSArray )backtrace
    {
    void
    callstack[128];
    int frames = backtrace(callstack, 128);
    char **strs = backtrace_symbols(callstack, frames);

    int i;
    NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
    for (
    i = UncaughtExceptionHandlerSkipAddressCount;
    i < UncaughtExceptionHandlerSkipAddressCount + UncaughtExceptionHandlerReportAddressCount;
    i++)
    {
    [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
    }
    free(strs);
    return backtrace;
    }
    objc
    注意,我們跳過了開始的一些地址:這是因為它們是信號和異常處理函數(shù)的地址(不重要)。由于我們想要保存最少的數(shù)據(jù)(用于在UIAlert對話框顯示),我選擇放棄顯示異常處理函數(shù)。

如果用戶選擇“退出”,我們將會記錄崩潰日志

如果用戶選擇“退出”來終止應(yīng)用,而不是繼續(xù)運行程序,用常用的崩潰日志處理來記錄記錄問題不失為一個好方法。

在本例中,我們將移除所有的異常處理器,并再次生成異?;蛘咧匦掳l(fā)送信號來使得程序像往常一樣崩潰(盡管未處理異常處理器會出現(xiàn)在棧的頂部,但后面的幀是相同的)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 這是一篇對Run Loop開發(fā)文檔《Threading Program Guide:Run Loops》的翻譯,來...
    鴻雁長飛光不度閱讀 3,846評論 3 29
  • 開發(fā)iOS應(yīng)用,解決Crash問題始終是一個難題。Crash分為兩種,一種是由EXC_BAD_ACCESS引起的,...
    流星Meteor閱讀 5,159評論 0 10
  • 由于文章長度限制,本文作為[譯]線程編程指南(一)后續(xù)部分。 Run Loops Run loop是與線程相關(guān)的基...
    巧巧的二表哥閱讀 1,270評論 0 5
  • 最近這篇一年以前的文章,遭到了別人的嘲諷,本篇的初衷就是自己記錄,測試也僅僅使用了數(shù)組越界來玩過,內(nèi)容可能比較單薄...
    190CM閱讀 5,586評論 8 8
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,639評論 19 139

友情鏈接更多精彩內(nèi)容