你们平时在开发过程当中,常常会遇到Crash,那也是在正常不过的事,可是做为一个优秀的iOS开发人员,必将这些用户不良体验降到最低。git
线下Crash
,咱们直接能够调试,结合stack信息,不难定位! 线上Crash
固然也有一些信息,毕竟苹果爸爸的产品仍是作得很是不错的!github
经过iPhone的Crashlog
也能够分析一些,可是这个是须要用户配合的,由于须要用户在手机中设置->诊断与用量->勾选 自动发送,而后在xcode中Window->Organizer->Crashes对应的app
,就是当前app最新一版本的crash log ,而且是解析过的,能够根据crash栈
等相关信息,尤为是程序代码级别的有超连接,一键能够直接跳转到程序崩溃的相关代码,这样更容易定位bug出处.数组
为了可以第一时间发现程序问题,应用程序须要实现本身的崩溃日志收集服务,成熟的开源项目不少,如**KSCrash,plcrashreporter,CrashKit**等。追求方便省心,对于保密性要求不高的程序来讲,也能够选择各类一条龙Crash统计产品,如 Crashlytics,Hockeyapp ,友盟,Bugly 等等xcode
可是,全部的可是,这不够!由于咱们再也不是一个简单会用的iOS开发人员,必将走向底层,了解原理,掌握装逼内容和技巧是咱们的必修课bash
Crash
的底层原理iOS系统自带的Apple’sCrashReporter
记录在设备中的Crash日志,ExceptionType
项一般会包含两个元素:Mach异常
和Unix信号
。服务器
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x041a6f3
复制代码
Mach异常是什么?它又是如何与Unix信号创建联系的?app
Mach
是一个XNU的微内核核心,Mach
异常是指最底层的内核级异常,被定义在下。每一个thread,task,host
都有一个异常端口数组,Mach
的部分API
暴露给了用户态,用户态的开发者能够直接经过MachAPI
设置thread,task,host
的异常端口,来捕获Mach
异常,抓取Crash
事件。函数
全部Mach
异常都在host
层被ux_exception
转换为相应的Unix信号
,并经过threadsignal
将信号投递到出错的线程。iOS中的POSIX API
就是经过Mach
之上的 BSD
层实现的。oop
所以,EXC_BAD_ACCESS(SIGSEGV)
表示的意思是:Mach
层的EXC_BAD_ACCESS异常
,在host
层被转换成SIGSEGV信号
投递到出错的线程。ui
KVO问题
NSNotification线程问题
数组越界
野指针
后台任务超时
内存爆出
主线程卡顿超阀值
死锁 .... 下面我就拿出最多见的两种Crash
分析一下
Exception
Signal
上面咱们也知道:既然最终以信号的方式投递到出错的线程,那么就能够经过注册相应函数来捕获信号.到达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
来讲吧,敲入prohand-ptrue-sfalseSIGABRT
命令,否则你啥也看不到。
而后断开断点,程序进入监听,下面剩下的操做就是包装异常,操做相似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回光返照咱们怎么继续保证应用程序稳定执行