1. 程式人生 > >iOS崩潰 捕獲異常處理

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點選了退出按鈕後執行的一系列程式碼。如果有知道的留言告訴我一下吧~~小女子不勝感激!