UncaughtExceptionHandler 捕捉異常

今天在看我們線上的bug的時候 ,突然想到是否可以直接在出現(xiàn) bug 和 異常的時候給出一個提醒啦,愕然一看發(fā)現(xiàn)老早就有人做了這工作啦 UncaughtExceptionHandler ,于是去看了下里面的實現(xiàn),特此記錄學習下。

再此之前,還是先回顧下基本的NSException

  • Exception關鍵字:
@try
{
// 可能會拋出異常的代碼
}
@catch(NSException* exception)
{
// 異常的處理代碼
}
@finally
{
// 不論是否有異常,總是會被執(zhí)行的代碼,通常用于 clean
}
  • NSException實例:
// 先創(chuàng)建一個異常對象
NSExcetpion * exception = [NSException exceptionWithName:...];
// 然后可以通過 
@throw exception; // 將其拋出 
或
[exception raise]; // 發(fā)送異常消息

平常我們直接在代碼中用的最多還是,@try -- @catch -- @finally, 另外 NSExcetpion 還可以對其進行Custom,繼承并擴展使用它。具體實例:Objective-C - 異常處理(NSException)


UncaughtExceptionHandler

UncaughtExceptionHandler 里面是利用 iOS SDK中提供了一個現(xiàn)成的函數(shù)NSSetUncaughtExceptionHandler 用來做異常處理的,通過拋出的Signal,專門對Signal處理。

void InstallUncaughtExceptionHandler(void) {
    NSSetUncaughtExceptionHandler(&HandleException);
    signal(SIGABRT, SignalHandler);
    signal(SIGILL, SignalHandler);
    signal(SIGSEGV, SignalHandler);
    signal(SIGFPE, SignalHandler);
    signal(SIGBUS, SignalHandler);
    signal(SIGPIPE, SignalHandler);
}

注意不同情況的 signal , 這等同于不同情況的異常。

void HandleException(NSException *exception)
{
    // 遞增的一個全局計數(shù)器,很快很安全,防止并發(fā)數(shù)太大
    int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
    if (exceptionCount > UncaughtExceptionMaximum) return;
    // 獲取 堆棧信息的數(shù)組
    NSArray *callStack = [UncaughtExceptionHandler backtrace];
    // 設置該字典
    NSMutableDictionary *userInfo =
    [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
    // 給 堆棧信息 設置 地址 Key
    [userInfo
     setObject:callStack
     forKey:UncaughtExceptionHandlerAddressesKey];

    // 假如崩潰了執(zhí)行 handleException: ,并且傳出 NSException
    [[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:) withObject:[NSException exceptionWithName:[exception name] reason:[exception reason]
      userInfo:userInfo]
     waitUntilDone:YES];
}
void SignalHandler(int signal)
{
    // 遞增的一個全局計數(shù)器,很快很安全,防止并發(fā)數(shù)太大
    int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
    if (exceptionCount > UncaughtExceptionMaximum) return;
    // 設置是哪一種 single 引起的問題
    NSMutableDictionary *userInfo =
    [NSMutableDictionary
     dictionaryWithObject:[NSNumber numberWithInt:signal]
     forKey:UncaughtExceptionHandlerSignalKey];
    // 獲取堆棧信息數(shù)組
    NSArray *callStack = [UncaughtExceptionHandler backtrace];
    // 寫入地址
    [userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
    //  假如崩潰了執(zhí)行 handleException: ,并且傳出 NSException
    [[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:)
                                                              withObject: [NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName reason: [NSString stringWithFormat:
       NSLocalizedString(@"Signal %d was raised.", nil),signal]
      
                                                                                            userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey]] waitUntilDone:YES];
}
void HandleException(NSException *exception)
{
    // 遞增的一個全局計數(shù)器,很快很安全,防止并發(fā)數(shù)太大
    int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
    if (exceptionCount > UncaughtExceptionMaximum) return;
    // 獲取 堆棧信息的數(shù)組
    NSArray *callStack = [UncaughtExceptionHandler backtrace];
    // 設置該字典
    NSMutableDictionary *userInfo =
    [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
    // 給 堆棧信息 設置 地址 Key
    [userInfo
     setObject:callStack
     forKey:UncaughtExceptionHandlerAddressesKey];

    // 假如崩潰了執(zhí)行 handleException: ,并且傳出 NSException
    [[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:) withObject:[NSException exceptionWithName:[exception name] reason:[exception reason]
      userInfo:userInfo]
     waitUntilDone:YES];
}

此處注意OSAtomicIncrement32的使用,它此處是一個遞增的一個全局計數(shù)器,效果又快又安全,是為了防止并發(fā)數(shù)太大出現(xiàn)錯誤的情況。

+ (NSArray *)backtrace
{
    void* callstack[128];
    //  該函數(shù)用來獲取當前線程調用堆棧的信息,獲取的信息將會被存放在buffer中(callstack),它是一個指針數(shù)組。
    int frames = backtrace(callstack, 128);
    //  backtrace_symbols將從backtrace函數(shù)獲取的信息轉化為一個字符串數(shù)組.
    char **strs = backtrace_symbols(callstack, frames);
    NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
    for (
         int i = UncaughtExceptionHandlerSkipAddressCount;
         i < UncaughtExceptionHandlerSkipAddressCount + UncaughtExceptionHandlerReportAddressCount;
         i++)
    {
        [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
    }
    free(strs); // 記得free
    return backtrace;
}

這邊值得注意的是下面這兩個函數(shù)方法

int backtrace(void**,int) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
char** backtrace_symbols(void* const*,int) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

該函數(shù)用來獲取當前線程調用堆棧的信息,并且轉化為字符串數(shù)組。

最后再來處理,此處涉及到 CFRunLoopRunInMode, kill 值得注意!

- (void)handleException:(NSException *)exception
{
     // 打印或彈出框
     // TODO :
          
    // 接到程序崩潰時的信號進行自主處理
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
    while (!dismissed)
    {
        for (NSString *mode in (NSArray *)allModes) {
            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];
    }
}

話說回來,這個UncaughtExceptionHandler 代碼量不多,但真的涉及到東西好多,好多都可以深入細扣,如有興趣和時間可以慢慢深入研究源碼,以及源碼背后的東東。

當然不足的是,并不是所有的程序崩潰都是能可以捕捉到異常的,有些時候是因為內存等一些其他的錯誤導致程序的崩潰,這樣的情況就不能通過這個方法直接獲取到啦。 同時Apple并不建議我們拋出異常,耗費的資源太大,所以此處作為了解學習比較適宜。

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

相關閱讀更多精彩內容

  • 程序都會有bug,那么在有bug和異常時能不能給個提醒呢,今天咱們來看看這個問題怎么解決 首先說下基本的NSExc...
    愛吃魚的小灰閱讀 731評論 1 9
  • 轉載:http://www.cnblogs.com/lulipro/p/7504267.html 一、異常簡介 程...
    SinX竟然被占用了閱讀 1,055評論 2 2
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,823評論 18 399
  • 最近項目上需要對崩潰信息進行處理,要滿足崩潰后及時捕捉到崩潰信息,當應用下次打開后再將報文上傳至服務器...
    迷失之刃閱讀 4,816評論 9 8
  • 1.當好跟班 女人最喜歡的就是買買買,逛商場時如果丈夫能陪同,而且隨時從妻子手里接過來買好的東西,妻子會感覺很自豪...
    魚笨自由閱讀 2,902評論 0 7

友情鏈接更多精彩內容