一、編碼常見崩潰
1 數(shù)組越界
2 多線程問題
3 程序無響應(yīng)
4 野指針
二、捕獲崩潰問題
1 可捕獲的崩潰信號(hào)
KVO、Notification線程問題、數(shù)組越界、野指針
收集這些崩潰日志的常用方法:
1 Xcode -> Archive
2 PLCrashReporter 獲取崩潰日志,上傳到自己的服務(wù)器查看
3 Fabric
4 Bugly
捕獲原理:
程序發(fā)生崩潰時(shí),我們經(jīng)常會(huì)看到這段代碼:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
它表示,EXC_BAD_ACCESS這個(gè)異常會(huì)通過SIGSEGV信號(hào)發(fā)現(xiàn)問題。
可以通過 signalHandler 來捕獲不同種類的信號(hào),代碼如下:
void registerSignalHandler(void) {
signal(SIGSEGV, handleSignalException);
signal(SIGFPE, handleSignalException);
signal(SIGBUS, handleSignalException);
signal(SIGPIPE, handleSignalException);
signal(SIGHUP, handleSignalException);
signal(SIGINT, handleSignalException);
signal(SIGQUIT, handleSignalException);
signal(SIGABRT, handleSignalException);
signal(SIGILL, handleSignalException);
}
void handleSignalException(int signal) {
NSMutableString *crashString = [[NSMutableString alloc]init];
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** traceChar = backtrace_symbols(callstack, frames);
for (i = 0; i <frames; ++i) {
[crashString appendFormat:@"%s\n", traceChar[i]];
}
NSLog(crashString);
}
上面這段代碼對(duì)各種信號(hào)都進(jìn)行了注冊(cè),捕獲到異常信號(hào)后,在處理方法 handleSignalException 里通過 backtrace_symbols 方法就能獲取到當(dāng)前的堆棧信息。
在程序崩潰前,將錯(cuò)誤的堆棧信息保存在本地磁盤中,下次啟動(dòng)時(shí)再上傳到服務(wù)器。
2 不可捕獲的崩潰信號(hào)
后臺(tái)任務(wù)超時(shí)、內(nèi)存打爆、主線程卡頓超閾值
App 退到后臺(tái)中,即使代碼邏輯沒有問題也很容易出現(xiàn)崩潰,這些崩潰往往是因?yàn)橄到y(tǒng)強(qiáng)制殺掉了某些進(jìn)程導(dǎo)致的,導(dǎo)致強(qiáng)殺的信號(hào)還由于系統(tǒng)限制無法被捕獲到。
后臺(tái)容易崩潰的原因是什么?
iOS后臺(tái)保活的方式有5種:Background Mode、Background Fetch、Silent Push、PushKit、Background Task。
每種方式都有不同的使用場(chǎng)景,這里就不一一介紹了,感興趣的朋友可以自己去了解一下。
這5種方式中,Background Task最為常用,App退到后臺(tái)后,默認(rèn)都會(huì)使用這種方式。
通過UIApplication的beginBackgroundTaskWithExpirationHandler:保持程序在后臺(tái)運(yùn)行


我們必須知道的是,App進(jìn)入后臺(tái)后,你的任務(wù)最終執(zhí)行的時(shí)間是有限的。(這個(gè)時(shí)間具體是多少?zèng)]有被考證,戴老師原文中說是3分鐘,上面提供的截圖中說的是10分鐘,截圖來自《iOS高級(jí)編程》)
總之,在有限的時(shí)間內(nèi)沒有執(zhí)行完任務(wù),系統(tǒng)也會(huì)強(qiáng)制殺掉進(jìn)程。而后臺(tái)處理的任務(wù)分門別類,所以很容易出現(xiàn)崩潰。尤其是要控制后臺(tái)讀寫操作。
如何監(jiān)控內(nèi)存打爆、主線程卡頓超閾值?
內(nèi)存打爆和主線程卡頓超過閾值被 watchdog 殺掉這兩種情況也會(huì)造成程序崩潰。
監(jiān)控他們的思路和監(jiān)控后臺(tái)崩潰類似,我們要先找到它們的閾值,然后在臨近閾值時(shí)還在執(zhí)行的后臺(tái)程序判斷為將要崩潰,收集信息并上報(bào)。(關(guān)于內(nèi)存和卡頓閾值怎么獲取和RunLoop有關(guān),后面會(huì)介紹)
三、分析和解決崩潰問題
對(duì)于捕獲的系統(tǒng)的崩潰日志,主要包含的信息為:進(jìn)程信息、基本信息、異常信息、線程回溯。
- 進(jìn)程信息:崩潰進(jìn)程的相關(guān)信息,比如崩潰報(bào)告唯一標(biāo)識(shí)符、唯一鍵值、設(shè)備標(biāo)識(shí)
- 基本信息:崩潰發(fā)生的日期、iOS版本
- 異常信息:異常類型、異常編碼、異常的線程
- 線程回溯:崩潰時(shí)的方法調(diào)用棧
方法1: 查看方法調(diào)用棧
先通過異常信息分析出發(fā)生異常的線程,然后分析方法調(diào)用棧,符號(hào)化后的方法調(diào)用棧可以完整的看到方法調(diào)用的過程,從而知道問題發(fā)生在哪個(gè)方法的調(diào)用上。

棧頂?shù)姆椒ň褪亲詈髮?dǎo)致崩潰的方法調(diào)用。
方法2: 分析異常編碼
一些被系統(tǒng)殺掉的情況,我們可以通過異常編碼來分析。
這里舉例3種常見的異常編碼:
- 0x8badf00d,表示 App 在一定時(shí)間內(nèi)無響應(yīng)二倍 watchdog 殺掉
- 0xdeadfa11,表示 App 被用戶強(qiáng)制退出
- 0xc00010ff,表示 App 因?yàn)檫\(yùn)行造成設(shè)備溫度太高而被殺掉
方法3: 分析第三方平臺(tái)采集的崩潰信息
之前我們提到的向 Bugly 平臺(tái),在監(jiān)控到崩潰信息后,它們會(huì)提供可視化的信息來輔助我們追溯問題。

最后
解決線上崩潰問題是一個(gè)非常復(fù)雜且繁瑣的工作,由于我個(gè)人在這方面也沒有很豐富的經(jīng)驗(yàn),所以此篇文章僅做學(xué)習(xí)和了解。
雖說現(xiàn)在可以使用的第三方監(jiān)控平臺(tái)很多,但是有些錯(cuò)誤發(fā)生后難以定位和復(fù)現(xiàn)。解決崩潰問題涉及的知識(shí)也遠(yuǎn)不止這些,除了上文的內(nèi)容,可能還涉及 RunLoop 的相關(guān)知識(shí)、App對(duì)設(shè)備性能及電量的影響、App 的全量日志信息等知識(shí)。
太多內(nèi)容導(dǎo)致我無法去一一實(shí)踐和測(cè)試,做技術(shù)就是要不斷的學(xué)習(xí),希望自己能堅(jiān)持。