你們平時在開發過程當中,常常會遇到Crash
,那也是在正常不過的事,可是做爲一個優秀的iOS開發人員,必將這些用戶不良體驗降到最低。git
Crash
,咱們直接能夠調試,結合stack
信息,不難定位!Crash
固然也有一些信息,畢竟蘋果爸爸的產品仍是作得很是不錯的!經過iPhone的Crash log
也能夠分析一些,可是這個是須要用戶配合的,由於須要用戶在手機 中 設置-> 診斷與用量->勾選 自動發送 ,而後在xcode中 Window->Organizer->Crashes 對應的app
,就是當前app最新一版本的crash log
,而且是解析過的,能夠根據crash 棧
等相關信息 ,尤爲是程序代碼級別的 有超連接,一鍵能夠直接跳轉到程序崩潰的相關代碼,這樣更容易定位bug出處.github
爲了可以第一時間發現程序問題,應用程序須要實現本身的崩潰日誌收集服務,成熟的開源項目不少,如 KSCrash,plcrashreporter,CrashKit 等。追求方便省心,對於保密性要求不高的程序來講,也能夠選擇各類一條龍Crash統計產品,如 Crashlytics,Hockeyapp ,友盟,Bugly 等等數組
可是,全部的可是,這不夠!由於咱們再也不是一個簡單會用的iOS開發人員,必將走向底層,瞭解原理,掌握裝逼內容和技巧是咱們的必修課xcode
Crash
的底層原理iOS系統自帶的 Apple’s Crash Reporter
記錄在設備中的Crash日誌,Exception Type
項一般會包含兩個元素:Mach異常
和 Unix信號
。bash
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x041a6f3
複製代碼
Mach異常是什麼?它又是如何與Unix信號創建聯繫的?服務器
Mach
是一個XNU的微內核核心,Mach
異常是指最底層的內核級異常,被定義在下 。每一個thread,task,host
都有一個異常端口數組,Mach
的部分API
暴露給了用戶態,用戶態的開發者能夠直接經過Mach API
設置thread,task,host
的異常端口,來捕獲Mach
異常,抓取Crash
事件。微信
全部Mach
異常都在host
層被ux_exception
轉換爲相應的Unix信號
,並經過threadsignal
將信號投遞到出錯的線程。iOS中的 POSIX API
就是經過Mach
之上的 BSD
層實現的。app
所以,EXC_BAD_ACCESS (SIGSEGV)
表示的意思是:Mach
層的EXC_BAD_ACCESS異常
,在host
層被轉換成SIGSEGV信號
投遞到出錯的線程。函數
KVO問題oop
NSNotification線程問題
數組越界
野指針
後臺任務超時
內存爆出
主線程卡頓超閥值
死鎖 .... 下面我就拿出最多見的兩種Crash
分析一下
Exception
Signal
Crash
分析處理上面咱們也知道:既然最終以信號的方式投遞到出錯的線程,那麼就能夠經過註冊相應函數來捕獲信號.到達Hook
的效果
+ (void)installUncaughtSignalExceptionHandler{
NSSetUncaughtExceptionHandler(&LGExceptionHandlers);
signal(SIGABRT, LGSignalHandler);
}
複製代碼
咱們從上面的函數能夠Hook到信息,下面咱們開始進行包裝處理.這裏仍是面向統一封裝,由於等會咱們還須要考慮Signal
void LGExceptionHandlers(NSException *exception) {
NSLog(@"%s",__func__);
NSArray *callStack = [LGUncaughtExceptionHandle lg_backtrace];
NSMutableDictionary *mDict = [NSMutableDictionary dictionaryWithDictionary:exception.userInfo];
[mDict setObject:callStack forKey:LGUncaughtExceptionHandlerAddressesKey];
[mDict setObject:exception.callStackSymbols forKey:LGUncaughtExceptionHandlerCallStackSymbolsKey];
[mDict setObject:@"LGException" forKey:LGUncaughtExceptionHandlerFileKey];
// exception - myException
[[[LGUncaughtExceptionHandle alloc] init] performSelectorOnMainThread:@selector(lg_handleException:) withObject:[NSException exceptionWithName:[exception name] reason:[exception reason] userInfo:mDict] waitUntilDone:YES];
}
複製代碼
下面針對封裝好的myException
進行處理,在這裏要作兩件事
BUG
閃退在用戶的手機上面,但願「起死回生,迴光返照」- (void)lg_handleException:(NSException *)exception{
// crash 處理
// 存
NSDictionary *userInfo = [exception userInfo];
[self saveCrash:exception file:[userInfo objectForKey:LGUncaughtExceptionHandlerFileKey]];
}
複製代碼
下面是一些封裝的一些輔助函數
- (void)saveCrash:(NSException *)exception file:(NSString *)file{
NSArray *stackArray = [[exception userInfo] objectForKey:LGUncaughtExceptionHandlerCallStackSymbolsKey];// 異常的堆棧信息
NSString *reason = [exception reason];// 出現異常的緣由
NSString *name = [exception name];// 異常名稱
// 或者直接用代碼,輸入這個崩潰信息,以便在console中進一步分析錯誤緣由
// NSLog(@"crash: %@", exception);
NSString * _libPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:file];
if (![[NSFileManager defaultManager] fileExistsAtPath:_libPath]){
[[NSFileManager defaultManager] createDirectoryAtPath:_libPath withIntermediateDirectories:YES attributes:nil error:nil];
}
NSDate *dat = [NSDate dateWithTimeIntervalSinceNow:0];
NSTimeInterval a=[dat timeIntervalSince1970];
NSString *timeString = [NSString stringWithFormat:@"%f", a];
NSString * savePath = [_libPath stringByAppendingFormat:@"/error%@.log",timeString];
NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];
BOOL sucess = [exceptionInfo writeToFile:savePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"保存崩潰日誌 sucess:%d,%@",sucess,savePath);
}
複製代碼
+ (NSArray *)lg_backtrace{
void* callstack[128];
int frames = backtrace(callstack, 128);//用於獲取當前線程的函數調用堆棧,返回實際獲取的指針個數
char **strs = backtrace_symbols(callstack, frames);//從backtrace函數獲取的信息轉化爲一個字符串數組
int i;
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (i = LGUncaughtExceptionHandlerSkipAddressCount;
i < LGUncaughtExceptionHandlerSkipAddressCount+LGUncaughtExceptionHandlerReportAddressCount;
i++)
{
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
複製代碼
Siganl
數據封裝NSString *getAppInfo(){
NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@)\nDevice : %@\nOS Version : %@ %@\n",
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"],
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
[UIDevice currentDevice].model,
[UIDevice currentDevice].systemName,
[UIDevice currentDevice].systemVersion];
// [UIDevice currentDevice].uniqueIdentifier];
NSLog(@"Crash!!!! %@", appInfo);
return appInfo;
}
複製代碼
作完這些準備,你能夠很是清晰的看到程序奔潰,哈哈哈!(好像之前奔潰還不清晰似的),這裏說一下:個人意思你很是清晰的知道奔潰以前作了一些什麼! 下面是檢測咱們奔潰以前的沙盒存儲的信息:error.log
下面咱們來一個騷操做:在監聽的信息的時候來了一個Runloop
,咱們監聽全部的mode
,開啓循環**(一個相對於咱們應用程序自啓的Runloop
的平行空間)**.
SCLAlertView *alert = [[SCLAlertView alloc] initWithNewWindowWidth:300.0f];
[alert addButton:@"奔潰" actionBlock:^{
self.dismissed = YES;
}];
[alert showSuccess:exception.name subTitle:exception.reason closeButtonTitle:nil duration:0];
// 本次異常處理
CFRunLoopRef runloop = CFRunLoopGetCurrent();
CFArrayRef allMode = CFRunLoopCopyAllModes(runloop);
while (!self.dismissed) {
// machO
// 後臺更新 - log
// kill
//
for (NSString *mode in (__bridge NSArray *)allMode) {
CFRunLoopRunInMode((CFStringRef)mode, 0.0001, false);
}
}
CFRelease(allMode);
複製代碼
在這個平行空間
咱們開啓一個彈框,這個彈框,跟着咱們的應用程序保活,而且具有相應的響應能力,到目前爲止:此時此刻還有誰!這不就是迴光返照
?只要咱們的條件成立,那麼在相應的這個平行空間
繼續作一些咱們的工做,程序不死:what is dead may never die,but rises again harder and stronger
在debug模式下,若是你觸發了崩潰,那麼應用會直接崩潰到主函數,斷點都沒用,此時沒有任何log信息顯示出來,若是你想看log信息的話,你須要在lldb
中,拿SIGABRT
來講吧,敲入pro hand -p true -s false SIGABRT
命令,否則你啥也看不到。
而後斷開斷點,程序進入監聽,下面剩下的操做就是包裝異常,操做相似Exception
最後咱們須要注意的針對咱們的監聽回收相應內存:
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];
}
複製代碼
到目前爲止,咱們響應的Crash
處理已經入門,若是你還想繼續探索也是有不少地方好比:
NSSetUncaughtExceptionHandler
,已達到拒絕傳遞 UncaughtExceptionHandler
的效果Runloop迴光返照
,有沒有更加合適的方法Runloop迴光返照
咱們怎麼繼續保證應用程序穩定執行若是大家有相應比較好的方式方法均可以直接留言,或者微信聯繫我
PS:加微信,請註明你的來意,謝謝!!
我就是我,顏色不同的煙火,我是Cooci,和諧學習,不急不躁!