在iOS开发中经常需要靠记录日志来调试应用程序、解决崩溃问题等,整理常用的日志输出和崩溃日志分析。
最新更新:2018-11-30
基于CocoaLumberjack 的 Swift使用封装库
一、崩溃的捕获
1、崩溃日志产生原因
1、应用中有Bug。 2、Watchdog 超时机制 3、用户强制退出 4、低内存终止 5、其他违反系统规则的操作,大部分是内存问题 发生崩溃,系统会生成一份崩溃日志在本地,或者上传 ITC
2、崩溃的类型(异常、信号错误)
异常类
NSRangeException等 NSException类
信号错误类
信号中断(SGIABRT)、非法指令信号(SIGILL)、总线错误信号(SIGBUS)、段错误信号(SIGSEGV)、访问一个已经释放的对象(EXC_BAD_ACCESS)
3、捕获异常崩溃信息
只能捕获一些异常崩溃,如 unrecognized selector、NSRangeException beyond bounds越界等Exception属错误
Appdelegate
在Appdelegate 的 didFinishLaunchingWithOptions 中 添加 NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler); 方法实现如下 void UncaughtExceptionHandler(NSException *exception) { /** * 获取异常崩溃信息 */ NSArray *callStack = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name]; NSString *content = [NSString stringWithFormat:@"========异常错误报告========/nname:%@/nreason:/n%@/ncallStackSymbols:/n%@",name,reason,[callStack componentsJoinedByString:@"/n"]]; //将崩溃信息持久化在本地,下次程序启动时、或者后台,将崩溃信息作为日志发送给开发者。 [[NSUserDefaults standardUserDefaults] setObject:content forKey:@"ExceptionContent"]; }
测试
数组越界错误 NSMutableArray *array = [NSMutableArray array]; NSLog(@"%@",array[1]);
4、捕获信号错误崩溃信息
信号类型崩溃捕获,测试的时候如果测试Signal类型的崩溃,不要在xcode下的debug模式进行测试。因为系统的debug会优先去拦截。应该build好应用之后直接点击运行app进行测试。
1、什么是信号
在计算机科学中,信号(英语:Signals)是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。
在iOS中就是未被捕获的Objective-C异常(NSException),导致程序向自身发送了SIGABRT信号而崩溃。
SIGABRT–程序中止命令中止信号 SIGALRM–程序超时信号 SIGFPE–程序浮点异常信号 SIGILL–程序非法指令信号 SIGHUP–程序终端中止信号 SIGINT–程序键盘中断信号 SIGKILL–程序结束接收中止信号 SIGTERM–程序kill中止信号 SIGSTOP–程序键盘中止信号 SIGSEGV–程序无效内存中止信号 SIGBUS–程序内存字节未对齐中止信号 SIGPIPE–程序Socket发送失败中止信号
2、捕获方法
Appdelegate 的 didFinishLaunchingWithOptions 中 添加 signal(SIGHUP, SignalHandler); signal(SIGINT, SignalHandler); signal(SIGQUIT, SignalHandler); signal(SIGABRT, SignalHandler); signal(SIGILL, SignalHandler); signal(SIGSEGV, SignalHandler); signal(SIGFPE, SignalHandler); signal(SIGBUS, SignalHandler); signal(SIGPIPE, SignalHandler); 方法实现如下 void SignalHandler(int signal){ int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount); if (exceptionCount > UncaughtExceptionMaximum) { return; } void* callstack[128]; int frames = backtrace(callstack, 128); char **strs = backtrace_symbols(callstack, frames); int i; NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames]; for ( i = UncaughtExceptionHandlerSkipAddressCount; i < UncaughtExceptionHandlerSkipAddressCount + UncaughtExceptionHandlerReportAddressCount; i++) { [backtrace addObject:[NSString stringWithUTF8String:strs[i]]]; } free(strs); NSLog(@"%@",backtrace); NSLog(@"%@", [NSString stringWithFormat: NSLocalizedString(@"Signal %d was raised.", nil), signal]); }
3、测试
UIView *tempView = [[UIView alloc]init]; [tempView release]; //对象已经被释放,内存不合法,此块内存地址又没被覆盖,所以此内存内垃圾内存,所以调用方法的时候会导致SIGSEGV的错误 [tempView setNeedsDisplay]; 或者说 我在堆内存中找栈内存地址 id x_id = [self performSelector:@selector(createNum)]; - (int)createNum { return 10; } 这种情况也是会导致SIGSEGV的错误的 如果在内存中释放不存在的空间,就会导致SIGABRT错误 Test * test = {1, 2}; free(test); 内存地址不对齐会导致SIGBUS错误 char *s = "hello world"; *s = 'H';
4、问题
信号捕获后 app卡死了 大部分这类型的错误会报错 EXC_BAD_ACCESS ,而这种错误都是发生在内存问题,例如 1、访问数据为空、数据类型不对 2、操作了不该操作的对象,野指针
5、EXC_BAD_ACCESS错误的调试
1、xcode可以用僵尸模式打印出对象 然后通过对象查找对应的代码位置
1、Edit Scheme - Diagnositics - Memory Management 勾选 Zombie Objects 和 Malloc Stack 2、会打印出 cyuyan[7756:17601127] *** -[UIViewController respondsToSelector:]: message sent to deallocated instance 0x7fe71240d390 这句开启僵尸模式后打出来的输出,包含了我们需要的 进程pid、崩溃地址,终端通过下面命令查看堆栈日志来找到崩溃代码 3、查找日志 sudo malloc_history 7756 0x7fe71240d390
2、覆写一个object的respondsToSelector方法
在 other c flags中加入-D FOR_DEBUG(记住请只在Debug Configuration下加入此标记)。这样当你程序崩溃时,Xcode的console上就会准确地记录了最后运行的object的方法。重写一个object的respondsToSelector方法,打印报错前的 #ifdef _FOR_DEBUG_ -(BOOL) respondsToSelector:(SEL)aSelector { printf("SELECTOR: %s/n", [NSStringFromSelector(aSelector) UTF8String]); return [super respondsToSelector:aSelector]; } #endif
3、通过instruments的Zombies
引申:怎么定位到野指针的地方。如果还没定位到,这个对象被提前释放了,怎么知道该对象在什么地方释放的
在Appdelegate 的 didFinishLaunchingWithOptions 中 添加 NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler); 方法实现如下 void UncaughtExceptionHandler(NSException *exception) { /** * 获取异常崩溃信息 */ NSArray *callStack = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name]; NSString *content = [NSString stringWithFormat:@"========异常错误报告========/nname:%@/nreason:/n%@/ncallStackSymbols:/n%@",name,reason,[callStack componentsJoinedByString:@"/n"]]; //将崩溃信息持久化在本地,下次程序启动时、或者后台,将崩溃信息作为日志发送给开发者。 [[NSUserDefaults standardUserDefaults] setObject:content forKey:@"ExceptionContent"]; }
0
6、可能崩溃的一些场景
1、野指针
在Appdelegate 的 didFinishLaunchingWithOptions 中 添加 NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler); 方法实现如下 void UncaughtExceptionHandler(NSException *exception) { /** * 获取异常崩溃信息 */ NSArray *callStack = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name]; NSString *content = [NSString stringWithFormat:@"========异常错误报告========/nname:%@/nreason:/n%@/ncallStackSymbols:/n%@",name,reason,[callStack componentsJoinedByString:@"/n"]]; //将崩溃信息持久化在本地,下次程序启动时、或者后台,将崩溃信息作为日志发送给开发者。 [[NSUserDefaults standardUserDefaults] setObject:content forKey:@"ExceptionContent"]; }
1
2、内存处理不当、内存泄露
3、主线程UI长时间卡死,系统强制销毁app
死锁?
4、多线程切换访问引起的crash
多线程抢写数据库?
5、和服务端约定的数据结构变更导致操作数据类型问题、或者操作空
二、崩溃日志的获取
1、iOS设备可以直接查看
在Appdelegate 的 didFinishLaunchingWithOptions 中 添加 NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler); 方法实现如下 void UncaughtExceptionHandler(NSException *exception) { /** * 获取异常崩溃信息 */ NSArray *callStack = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name]; NSString *content = [NSString stringWithFormat:@"========异常错误报告========/nname:%@/nreason:/n%@/ncallStackSymbols:/n%@",name,reason,[callStack componentsJoinedByString:@"/n"]]; //将崩溃信息持久化在本地,下次程序启动时、或者后台,将崩溃信息作为日志发送给开发者。 [[NSUserDefaults standardUserDefaults] setObject:content forKey:@"ExceptionContent"]; }
2
2、链接设备到电脑 Itunes同步后,日志会保存在电脑上
在Appdelegate 的 didFinishLaunchingWithOptions 中 添加 NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler); 方法实现如下 void UncaughtExceptionHandler(NSException *exception) { /** * 获取异常崩溃信息 */ NSArray *callStack = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name]; NSString *content = [NSString stringWithFormat:@"========异常错误报告========/nname:%@/nreason:/n%@/ncallStackSymbols:/n%@",name,reason,[callStack componentsJoinedByString:@"/n"]]; //将崩溃信息持久化在本地,下次程序启动时、或者后台,将崩溃信息作为日志发送给开发者。 [[NSUserDefaults standardUserDefaults] setObject:content forKey:@"ExceptionContent"]; }
3
3、xcode获取
在Appdelegate 的 didFinishLaunchingWithOptions 中 添加 NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler); 方法实现如下 void UncaughtExceptionHandler(NSException *exception) { /** * 获取异常崩溃信息 */ NSArray *callStack = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name]; NSString *content = [NSString stringWithFormat:@"========异常错误报告========/nname:%@/nreason:/n%@/ncallStackSymbols:/n%@",name,reason,[callStack componentsJoinedByString:@"/n"]]; //将崩溃信息持久化在本地,下次程序启动时、或者后台,将崩溃信息作为日志发送给开发者。 [[NSUserDefaults standardUserDefaults] setObject:content forKey:@"ExceptionContent"]; }
4
4、线上的崩溃,没有设备
在Appdelegate 的 didFinishLaunchingWithOptions 中 添加 NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler); 方法实现如下 void UncaughtExceptionHandler(NSException *exception) { /** * 获取异常崩溃信息 */ NSArray *callStack = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name]; NSString *content = [NSString stringWithFormat:@"========异常错误报告========/nname:%@/nreason:/n%@/ncallStackSymbols:/n%@",name,reason,[callStack componentsJoinedByString:@"/n"]]; //将崩溃信息持久化在本地,下次程序启动时、或者后台,将崩溃信息作为日志发送给开发者。 [[NSUserDefaults standardUserDefaults] setObject:content forKey:@"ExceptionContent"]; }
5
三、崩溃日志的解析
1、崩溃日志的实例
在Appdelegate 的 didFinishLaunchingWithOptions 中 添加 NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler); 方法实现如下 void UncaughtExceptionHandler(NSException *exception) { /** * 获取异常崩溃信息 */ NSArray *callStack = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name]; NSString *content = [NSString stringWithFormat:@"========异常错误报告========/nname:%@/nreason:/n%@/ncallStackSymbols:/n%@",name,reason,[callStack componentsJoinedByString:@"/n"]]; //将崩溃信息持久化在本地,下次程序启动时、或者后台,将崩溃信息作为日志发送给开发者。 [[NSUserDefaults standardUserDefaults] setObject:content forKey:@"ExceptionContent"]; }
6
本文由 投稿者 创作,文章地址:https://blog.isoyu.com/archives/iosbengkuiyurizhifenxi.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:12 月 4, 2018 at 10:55 下午