本文轉載於:做者:Cooci_和諧學習_不急不躁
連接:www.jianshu.com/p/56f96167a…git
你們平時在開發過程當中,常常會遇到Crash
,那也是在正常不過的事,可是做爲一個優秀的iOS開發人員,必將這些用戶不良體驗降到最低。github
經過iPhone的Crash log
也能夠分析一些,可是這個是須要用戶配合的,由於須要用戶在手機 中 設置-> 診斷與用量->勾選 自動發送 ,而後在xcode中 Window->Organizer->Crashes 對應的app
,就是當前app最新一版本的crash log
,而且是解析過的,能夠根據crash 棧
等相關信息 ,尤爲是程序代碼級別的 有超連接,一鍵能夠直接跳轉到程序崩潰的相關代碼,這樣更容易定位bug出處.面試
爲了可以第一時間發現程序問題,應用程序須要實現本身的崩潰日誌收集服務,成熟的開源項目不少,如 KSCrash,plcrashreporter,CrashKit 等。追求方便省心,對於保密性要求不高的程序來講,也能夠選擇各類一條龍Crash統計產品,如 Crashlytics,Hockeyapp ,友盟,Bugly 等等算法
可是,全部的可是,這不夠!由於咱們再也不是一個簡單會用的iOS開發人員,必將走向底層,瞭解原理,掌握裝逼內容和技巧是咱們的必修課swift
Crash
的底層原理iOS系統自帶的 Apple’s Crash Reporter
記錄在設備中的Crash日誌,Exception Type
項一般會包含兩個元素:Mach異常
和Unix信號
。數組
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x041a6f3
複製代碼
Mach異常是什麼?它又是如何與Unix信號創建聯繫的?xcode
Mach
是一個XNU的微內核核心,Mach
異常是指最底層的內核級異常,被定義在下 。每一個thread,task,host
都有一個異常端口數組,Mach
的部分API
暴露給了用戶態,用戶態的開發者能夠直接經過Mach API
設置thread,task,host
的異常端口,來捕獲Mach
異常,抓取Crash
事件。bash
全部Mach
異常都在host
層被ux_exception
轉換爲相應的Unix信號
,並經過threadsignal
將信號投遞到出錯的線程。iOS中的 POSIX API
就是經過Mach
之上的 BSD
層實現的。服務器
EXC_BAD_ACCESS (SIGSEGV)
表示的意思是:
Mach
層的
EXC_BAD_ACCESS
異常,在
host
層被轉換成
SIGSEGV信號
投遞到出錯的線程。
KVO問題微信
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;
}
複製代碼
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
處理已經入門,若是你還想繼續探索也是有不少地方好比:
咱們可否hook系統奔潰,異常的方法NSSetUncaughtExceptionHandler
,已達到拒絕傳遞 UncaughtExceptionHandler
的效果
咱們在處理異常的時候,利用Runloop迴光返照
,有沒有更加合適的方法
Runloop迴光返照
咱們怎麼繼續保證應用程序穩定執行
想學習數據結構與算法、底層進階、swift、逆向、底層面試題整合文檔等的同窗能夠加我微信JuinDay,免費提供學習資料!