參考的文章是https://blog.csdn.net/u013602835/article/details/80485331
我們經(jīng)常會(huì)遇到APP閃退和崩潰的問(wèn)題测砂,那么我們應(yīng)該通過(guò)什么變量去監(jiān)聽APP的異常呢点弯?如何在程序崩潰時(shí)褐啡,保證程序不閃退,并給用戶彈出一個(gè)提示框呢? 這是本文將要講述的內(nèi)容停巷。
? ? 先介紹2個(gè)概念,Mach異常和Signal信號(hào)榕栏,如果想要監(jiān)聽異常其實(shí)就是去監(jiān)聽Mach異常和Signal信號(hào)畔勤。其實(shí)系統(tǒng)已經(jīng)給我們提供了一個(gè)方法去監(jiān)聽程序產(chǎn)生的異常,通過(guò)NSSetUncaughtExceptionHandler(入?yún)⑹且粋€(gè)C函數(shù))方法就可以直接捕獲異常信息扒磁。但是這個(gè)方法捕獲的異常有限庆揪,不能捕獲由于內(nèi)存訪問(wèn)錯(cuò)誤等產(chǎn)生的signal,所以如果想要監(jiān)聽絕大多數(shù)的異常需要我們自己通過(guò)注冊(cè)signal(signal的類型,回調(diào)方法)來(lái)捕獲信號(hào)進(jìn)行監(jiān)聽妨托。一缸榛、Mach異常
? ? Mach是Mac OS和iOS操作系統(tǒng)的微內(nèi)核核心检访,Mach異常是指最底層的內(nèi)核級(jí)異常 。每個(gè)thread仔掸,task都有一個(gè)異常端口數(shù)組脆贵,Mach的部分API暴露給了開發(fā)者,開發(fā)者可以直接通過(guò)Mach API設(shè)置thread起暮,task卖氨,host的異常端口,來(lái)監(jiān)聽捕獲Mach異常负懦,抓取Crash事件筒捺。所以當(dāng)APP中產(chǎn)生異常時(shí),最先能監(jiān)聽到異常的就是Mach纸厉。
二系吭、Signal信號(hào)
? ? 最先捕獲到異常的Mach在接下來(lái)會(huì)將所有的異常轉(zhuǎn)換為相應(yīng)的Unix信號(hào),并投遞到出錯(cuò)的線程颗品。之后就可以注冊(cè)想要監(jiān)聽的signal類型肯尺,來(lái)捕獲信號(hào)。如下躯枢,就是監(jiān)聽了SIGSEGV信號(hào)则吟,當(dāng)有SIGSEGV信號(hào)產(chǎn)生時(shí),就會(huì)回調(diào)mySignalHandler方法:
signal(SIGSEGV,mySignalHandler)
什么是Signal锄蹂?
? ? 在計(jì)算機(jī)科學(xué)中氓仲,信號(hào)(英語(yǔ):Signals)是Unix、類Unix以及其他POSIX兼容的操作系統(tǒng)中進(jìn)程間通訊的一種有限制的方式得糜。它是一種異步的通知機(jī)制敬扛,用來(lái)提醒進(jìn)程一個(gè)事件已經(jīng)發(fā)生。當(dāng)一個(gè)信號(hào)發(fā)送給一個(gè)進(jìn)程朝抖,操作系統(tǒng)中斷了進(jìn)程正常的控制流程啥箭,此時(shí),任何非原子操作都將被中斷槽棍。如果進(jìn)程定義了信號(hào)的處理函數(shù)捉蚤,那么它將被執(zhí)行抬驴,否則就執(zhí)行默認(rèn)的處理函數(shù)炼七。
? 如何使用Signal?
? ? ? 在項(xiàng)目工程中布持,要使用 Signal 時(shí)豌拙,通過(guò)引入 signal.h 來(lái)使用:
? ? ? #include <sys/signal.h>
? ? ? 在 sys/signal 文件內(nèi)定義了大量的系統(tǒng)信號(hào)標(biāo)識(shí)
使用這些信號(hào)標(biāo)識(shí),要通過(guò)函數(shù) void (*signal(int, void (*)(int)))(int); 來(lái)進(jìn)行使用,如下所示:
信號(hào)處理函數(shù)可以通過(guò) signal() 系統(tǒng)調(diào)用來(lái)設(shè)置题暖。如果沒有為一個(gè)信號(hào)設(shè)置對(duì)應(yīng)的處理函數(shù)按傅,就會(huì)使用默認(rèn)的處理函數(shù)捉超,否則信號(hào)就被進(jìn)程截獲并調(diào)用相應(yīng)的處理函數(shù)。在沒有處理函數(shù)的情況下唯绍,程序可以指定兩種行為:忽略這個(gè)信號(hào) SIG_IGN 或者用默認(rèn)的處理函數(shù) SIG_DFL 拼岳。但是有兩個(gè)信號(hào)是無(wú)法被截獲并處理的: SIGKILL、SIGSTOP 况芒。
Signal信號(hào)類型:
SIGABRT--程序中止命令中止信號(hào)
SIGALRM--程序超時(shí)信號(hào)
SIGFPE--程序浮點(diǎn)異常信號(hào)
SIGILL--程序非法指令信號(hào)
SIGHUP--程序終端中止信號(hào)
SIGINT--程序鍵盤中斷信號(hào)
SIGKILL--程序結(jié)束接收中止信號(hào)
SIGTERM--程序kill中止信號(hào)
SIGSTOP--程序鍵盤中止信號(hào)
SIGSEGV--程序無(wú)效內(nèi)存中止信號(hào)
SIGBUS--程序內(nèi)存字節(jié)未對(duì)齊中止信號(hào)
SIGPIPE--程序Socket發(fā)送失敗中止信號(hào)
三惜纸、當(dāng)APP崩潰時(shí)做線程保活绝骚,彈出程序異常提示框
UncaughtExceptionHandler里面是利用 iOS SDK中提供的現(xiàn)成函數(shù)NSSetUncaughtExceptionHandler 加上 注冊(cè)想要監(jiān)聽的signal類型耐版,來(lái)做異常處理的,通過(guò)拋出的Signal压汪,專門對(duì)Signal處理粪牲。?
void InstallUncaughtExceptionHandler(void) {
? ? NSSetUncaughtExceptionHandler(&HandleException); //系統(tǒng)的方法
? ? //添加想要監(jiān)聽的signal類型,當(dāng)發(fā)出相應(yīng)類型的signal時(shí)止剖,會(huì)回調(diào)SignalHandler方法
? ? signal(SIGABRT, SignalHandler);
? ? signal(SIGILL, SignalHandler);
? ? signal(SIGSEGV, SignalHandler);
? ? signal(SIGFPE, SignalHandler);
? ? signal(SIGBUS, SignalHandler);
? ? signal(SIGPIPE, SignalHandler);
}
void HandleException(NSException *exception)
{
? ? // 遞增的一個(gè)全局計(jì)數(shù)器腺阳,很快很安全,防止并發(fā)數(shù)太大
? ? int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
? ? if (exceptionCount > UncaughtExceptionMaximum) return;
? ? // 獲取 堆棧信息的數(shù)組
? ? NSArray *callStack = [UncaughtExceptionHandler backtrace];
? ? // 設(shè)置該字典
? ? NSMutableDictionary *userInfo =
? ? [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
? ? // 給 堆棧信息 設(shè)置 地址 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)
{
? ? // 遞增的一個(gè)全局計(jì)數(shù)器舌狗,很快很安全,防止并發(fā)數(shù)太大
? ? int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
? ? if (exceptionCount > UncaughtExceptionMaximum) return;
? ? // 設(shè)置是哪一種 single 引起的問(wèn)題
? ? 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];
}
此處注意OSAtomicIncrement32的使用痛侍,它此處是一個(gè)遞增的一個(gè)全局計(jì)數(shù)器,效果又快又安全魔市,是為了防止并發(fā)數(shù)太大出現(xiàn)錯(cuò)誤的情況主届。
+ (NSArray *)backtrace
{
? ? void* callstack[128];
? ? //? 該函數(shù)用來(lái)獲取當(dāng)前線程調(diào)用堆棧的信息,獲取的信息將會(huì)被存放在buffer中(callstack),它是一個(gè)指針數(shù)組。
? ? int frames = backtrace(callstack, 128);
? ? //? backtrace_symbols將從backtrace函數(shù)獲取的信息轉(zhuǎn)化為一個(gè)字符串?dāng)?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;
}
這邊值得注意的是下面這兩個(gè)函數(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ù)用來(lái)獲取當(dāng)前線程調(diào)用堆棧的信息待德,并且轉(zhuǎn)化為字符串?dāng)?shù)組君丁。
最后再來(lái)處理,此處涉及到 CFRunLoopRunInMode将宪, kill值得注意绘闷!
- (void)handleException:(NSException *)exception
{
? ? // 打印或彈出框
? ? // TODO :
? ? // 接到程序崩潰時(shí)的信號(hào)進(jìn)行自主處理
? ? CFRunLoopRef runLoop = CFRunLoopGetCurrent();
? ? CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
? ? while (!dismissed)
? ? {
? ? ? ? //循環(huán)切換mode
? ? ? ? for (NSString *mode in (NSArray *)allModes) {
? ? ? ? ? ? CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
? ? ? ? }
? ? }
? ? CFRelease(allModes);
? ? // 下面等同于清空之前設(shè)置的
? ? 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];
? ? }
}
? ? 當(dāng)異常產(chǎn)生時(shí),上邊代碼就可以捕獲Crash異常较坛,然后彈出一個(gè)程序異常提示框印蔗。
? ? 注意:NSSetUncaughtExceptionHandler方法在純Swift工程中不好使,無(wú)法監(jiān)聽到異常丑勤。
原文鏈接:https://blog.csdn.net/u013602835/article/details/80485331
總結(jié):如果想要監(jiān)聽異常其實(shí)就是去監(jiān)聽Mach異常和Signal信號(hào)华嘹。其實(shí)系統(tǒng)已經(jīng)給我們提供了一個(gè)方法去監(jiān)聽程序產(chǎn)生的異常,通過(guò)NSSetUncaughtExceptionHandler(入?yún)⑹且粋€(gè)C函數(shù))方法就可以直接捕獲異常信息法竞。但是這個(gè)方法捕獲的異常有限耙厚,不能捕獲由于內(nèi)存訪問(wèn)錯(cuò)誤等產(chǎn)生的signal强挫,所以如果想要監(jiān)聽絕大多數(shù)的異常需要我們自己通過(guò)注冊(cè)signal(signal的類型,回調(diào)方法)來(lái)捕獲信號(hào)進(jìn)行監(jiān)聽。
利用 iOS SDK中提供的現(xiàn)成函數(shù)NSSetUncaughtExceptionHandler 加上 注冊(cè)想要監(jiān)聽的signal類型薛躬,來(lái)做異常處理的俯渤,通過(guò)拋出的Signal,專門對(duì)Signal處理型宝。?
NSSetUncaughtExceptionHandler方法在純Swift工程中不好使稠诲,無(wú)法監(jiān)聽到異常。