RunLoop总结:RunLoop的应用场景(五)
今天要介紹的RunLoop應(yīng)用場(chǎng)景感覺很酷炫,我們可能不常用到,但是對(duì)于做Crash 收集的 SDK可能會(huì)用得比較頻繁吧。相比關(guān)于RunLoop 可以讓應(yīng)用起死回生,大家都聽說過,可是怎么實(shí)現(xiàn)呢?今天我就來實(shí)際試驗(yàn)一下。
資料
- sunnyxx線下分享RunLoop (這是一份關(guān)于線下分享與討論RunLoop的視頻,備用地址:https://pan.baidu.com/s/1pLm4Vf9)
- 漫談iOS Crash收集框架(簡(jiǎn)單介紹了下iOS 中Crash 的一些知識(shí)。)
- IOS程序異常crash捕獲與攔截 (我下面的Demo 就是在這部分代碼上做了簡(jiǎn)化,以方便理解)
原理
iOS應(yīng)用崩潰,常見的崩潰信息有EXC_BAD_ACCESS、SIGABRT XXXXXXX,而這里分為兩種情況,一種是未被捕獲的異常,我們只需要添加一個(gè)回調(diào)函數(shù),并在應(yīng)用啟動(dòng)時(shí)調(diào)用一個(gè) API即可;另一種是直接發(fā)送的 SIGABRT XXXXXXX,這里我們也需要監(jiān)聽各種信號(hào),然后添加回調(diào)函數(shù)。
針對(duì)情況一,其實(shí)我們都見過。我們?cè)谑占疉pp崩潰信息時(shí),需要添加一個(gè)函數(shù) NSSetUncaughtExceptionHandler(&HandleException),參數(shù) 是一個(gè)回調(diào)函數(shù),在回調(diào)函數(shù)里獲取到異常的原因,當(dāng)前的堆棧信息等保存到 dump文件,然后供下次打開App時(shí)上傳到服務(wù)器。
其實(shí),我們?cè)贖andleException回調(diào)函數(shù)中,可以獲取到當(dāng)前的RunLoop,然后獲取該RunLoop中的所有Mode,手動(dòng)運(yùn)行一遍。
針對(duì)情況二,首先針對(duì)多種要捕獲的信號(hào),設(shè)置好回調(diào)函數(shù),然后也是在回調(diào)函數(shù)中獲取RunLoop,然后拿到所有的Mode,手動(dòng)運(yùn)行一遍。
代碼實(shí)現(xiàn)
第一步,我創(chuàng)建了一個(gè)處理類,并添加一個(gè)單例方法。(代碼見末尾的Demo)
第二步,在單例中對(duì)象實(shí)例化時(shí),添加 異常捕獲 和 signal 處理的 回調(diào)函數(shù)。
- (void)setCatchExceptionHandler {// 1.捕獲一些異常導(dǎo)致的崩潰NSSetUncaughtExceptionHandler(&HandleException);// 2.捕獲非異常情況,通過signal傳遞出來的崩潰signal(SIGABRT, SignalHandler);signal(SIGILL, SignalHandler);signal(SIGSEGV, SignalHandler);signal(SIGFPE, SignalHandler);signal(SIGBUS, SignalHandler);signal(SIGPIPE, SignalHandler); }第三步,分別實(shí)現(xiàn) 異常捕獲的回調(diào) 和 signal 的回調(diào)。
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(@"信號(hào)捕獲崩潰,堆棧信息:%@",callStack);CrashHandler *crashObject = [CrashHandler sharedInstance];NSException *customException = [NSException exceptionWithName:kSignalExceptionNamereason:[NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.", nil),signal]userInfo:@{kSignalKey:[NSNumber numberWithInt:signal]}];[crashObject performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES]; }第四步,添加讓應(yīng)用起死回生的 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:selfcancelButtonTitle:@"崩就蹦吧"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];} }因?yàn)槲疫@里弄了一個(gè)AlertView彈窗,所以必須要回到主線程來處理。
實(shí)際上,RunLoop 相關(guān)的代碼:
完全可以寫在 上面的 HandleException 回調(diào) 和 SignalHandler回調(diào)中。
第五步,寫一段會(huì)導(dǎo)致崩潰的代碼
我是在ViewController 中添加了一個(gè)點(diǎn)擊事件,弄了一個(gè)數(shù)組越界的Bug:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {NSArray *array =[NSArray array];NSLog(@"%@",[array objectAtIndex:1]); }動(dòng)態(tài)效果圖:
sunnyxx 稱之為回光返照,為什么呢?
我再一次點(diǎn)擊視圖,應(yīng)用依然還是崩潰了,只能防止第一次崩潰。
我測(cè)試了,確實(shí)是第二次應(yīng)用崩潰,未能起死回生。
文中的示例代碼都來自:RunLoopDemos中的RunLoopDemo04
轉(zhuǎn)載于:https://www.cnblogs.com/wanghang/p/6298800.html
總結(jié)
以上是生活随笔為你收集整理的RunLoop总结:RunLoop的应用场景(五)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 时间控件之赋值问题:datetimebo
- 下一篇: tp中使用分页技术