RunLoop總結:RunLoop的應用場景(五)阻止App崩潰一次

今天要介紹的RunLoop應用場景感受很酷炫,咱們可能不經常使用到,可是對於作Crash 收集的 SDK可能會用得比較頻繁吧。相比關於RunLoop 可讓應用起死回生,你們都據說過,但是怎麼實現呢?今天我就來實際試驗一下。html

資料

原理

iOS應用崩潰,常見的崩潰信息有EXC_BAD_ACCESSSIGABRT XXXXXXX,而這裏分爲兩種狀況,一種是未被捕獲的異常,咱們只須要添加一個回調函數,並在應用啓動時調用一個 API便可;另外一種是直接發送的 SIGABRT XXXXXXX,這裏咱們也須要監聽各類信號,而後添加回調函數。ios

針對狀況一,其實咱們都見過。咱們在收集App崩潰信息時,須要添加一個函數 NSSetUncaughtExceptionHandler(&HandleException),參數 是一個回調函數,在回調函數裏獲取到異常的緣由,當前的堆棧信息等保存到 dump文件,而後供下次打開App時上傳到服務器。git

其實,咱們在HandleException回調函數中,能夠獲取到當前的RunLoop,而後獲取該RunLoop中的全部Mode,手動運行一遍。github

針對狀況二,首先針對多種要捕獲的信號,設置好回調函數,而後也是在回調函數中獲取RunLoop,而後拿到全部的Mode,手動運行一遍。數組

代碼實現

第一步,我建立了一個處理類,並添加一個單例方法。(代碼見末尾的Demo)bash

第二步,在單例中對象實例化時,添加 異常捕獲 和 signal 處理的 回調函數。服務器

- (void)setCatchExceptionHandler
{
    // 1.捕獲一些異常致使的崩潰
    NSSetUncaughtExceptionHandler(&HandleException);
    
    // 2.捕獲非異常狀況,經過signal傳遞出來的崩潰
    signal(SIGABRT, SignalHandler);
    signal(SIGILL, SignalHandler);
    signal(SIGSEGV, SignalHandler);
    signal(SIGFPE, SignalHandler);
    signal(SIGBUS, SignalHandler);
    signal(SIGPIPE, SignalHandler);
}
複製代碼

第三步,分別實現 異常捕獲的回調 和 signal 的回調。markdown

void HandleException(NSException *exception)
{
    // 獲取異常的堆棧信息
    NSArray *callStack = [exception callStackSymbols];
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    [userInfo setObject:callStack forKey:kCaughtExceptionStackInfoKey];
    
    CrashHandler *crashObject = [CrashHandler sharedInstance];
    NSException *customException = [NSException exceptionWithName:[exception name] reason:[exception reason] userInfo:userInfo];
    [crashObject performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES];
}

void SignalHandler(int signal)
{
    // 這種狀況的崩潰信息,就另某他法來捕獲吧
    NSArray *callStack = [CrashHandler backtrace];
    NSLog(@"信號捕獲崩潰,堆棧信息:%@",callStack);
    
    CrashHandler *crashObject = [CrashHandler sharedInstance];
    NSException *customException = [NSException exceptionWithName:kSignalExceptionName
                                                           reason:[NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.", nil),signal]
                                                         userInfo:@{kSignalKey:[NSNumber numberWithInt:signal]}];
    
    [crashObject performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES];
}
複製代碼

第四步,添加讓應用起死回生的 RunLoop 代碼框架

- (void)handleException:(NSException *)exception
{
    NSString *message = [NSString stringWithFormat:@"崩潰緣由以下:\n%@\n%@",
                         [exception reason],
                         [[exception userInfo] objectForKey:kCaughtExceptionStackInfoKey]];
    NSLog(@"%@",message);
    
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"程序崩潰了"
                                                    message:@"若是你能讓程序起死回生,那你的決定是?"
                                                   delegate:self
                                          cancelButtonTitle:@"崩就蹦吧"
                                          otherButtonTitles:@"起死回生", nil];
    [alert show];
    
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
    
    while (!ignore) {
        for (NSString *mode in (__bridge 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:kSignalExceptionName]) {
        kill(getpid(), [[[exception userInfo] objectForKey:kSignalKey] intValue]);
    } else {
        [exception raise];
    }
}
複製代碼

由於我這裏弄了一個AlertView彈窗,因此必需要回到主線程來處理。 實際上,RunLoop 相關的代碼:函數

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
    
    while (!ignore) {
        for (NSString *mode in (__bridge NSArray *)allModes) {
            CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
        }
    }
    
    CFRelease(allModes);
複製代碼

徹底能夠寫在 上面的 HandleException 回調 和 SignalHandler回調中。

第五步,寫一段會致使崩潰的代碼

我是在ViewController 中添加了一個點擊事件,弄了一個數組越界的Bug:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSArray *array =[NSArray array];
    NSLog(@"%@",[array objectAtIndex:1]);
}
複製代碼

動態效果圖:

遇到數組越界,應用依然沒崩潰

sunnyxx 稱之爲迴光返照,爲何呢? 我再一次點擊視圖,應用依然仍是崩潰了,只能防止第一次崩潰。 我測試了,確實是第二次應用崩潰,未能起死回生。

文中的示例代碼都來自:RunLoopDemos中的RunLoopDemo04

相關文章
相關標籤/搜索