今天要介紹的RunLoop應用場景感受很酷炫,咱們可能不經常使用到,可是對於作Crash 收集的 SDK可能會用得比較頻繁吧。相比關於RunLoop 可讓應用起死回生,你們都據說過,但是怎麼實現呢?今天我就來實際試驗一下。html
iOS應用崩潰,常見的崩潰信息有EXC_BAD_ACCESS
、SIGABRT 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