##一、前言
在日常开发中或者测试过程中,我们的应用可能会出现Crash的问题。对于这类问题我们要抱着零容忍的态度,因为如果线上出现了这类问题,将会严重影响用户的体验。
如果Crash出现的时候恰好是在开发过程中,那么开发者可以根据Xcode的调用堆栈或者控制台输出的信息来定位问题的原因。但是,如果是在测试过程中的话就比较麻烦了。常见的两种解决方案是:
- 直接把测试手机拿来连接Xcode查看设备信息中的日志。
- 需要测试同学给出Crash的复现路径,然后开发者在调试过程中进行复现。
不过,以上两种方式都不是很方便。那么问题来了,有没有更好的方式查看Crash日志?答案当然是肯定的。DoraemonKit的常用工具集中的Crash查看功能就解决了这个问题,可以直接在APP端查看Crash日志,下面我们来介绍下Crash查看功能的实现。
##二、技术实现
在iOS的开发过程中,会出现各种各样的Crash,那如何才能捕获这些不同的Crash呢?其实对于常见的Crash而言,可以分为两类,一类是Objective-C异常,另一类是Mach异常,一些常见的异常如下图所示:
下面,我们就来看下这两类异常应当如何捕获。
###2.1 Objective-C异常
顾名思义,Objective-C异常就是指在OC层面(iOS库、第三方库出现错误时)出现的异常。在介绍如何捕获Objective-C异常之前我们先来看下常见的Objective-C异常包括哪些。
####2.1.1 常见的Objective-C异常
一般来说,常见的Objective-C异常包括以下几种:
- NSInvalidArgumentException(非法参数异常)
这类异常的主要原因是没有对于参数的合法性进行校验,最常见的就是传入nil作为参数。例如,NSMutableDictionary添加key为nil的对象,测试代码如下:
NSString *key = nil;
NSString *value = @"Hello";
NSMutableDictionary *mDic = [[NSMutableDictionary alloc] init];
[mDic setObject:value forKey:key];
运行后控制台输出日志:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '*** -[__NSDictionaryM setObject:forKey:]: key cannot be nil'
- NSRangeException(越界异常)
这类异常的主要原因是没有对于索引进行合法性的检查,导致索引落在集合数据的合法范围之外。例如,索引超出数组的范围从而导致数组越界的问题,测试代码如下:
NSArray *array = @[@0, @1, @2];
NSUInteger index = 3;
NSNumber *value = [array objectAtIndex:index];
运行后控制台输出日志:
*** Terminating app due to uncaught exception 'NSRangeException',
reason: '*** -[__NSArrayI objectAtIndex:]: index 3 beyond bounds [0 .. 2]'
- NSGenericException(通用异常)
这类异常最容易出现在foreach操作中,主要原因是在遍历过程中进行了元素的修改。例如,在for in循环中如果修改所遍历的数组则会导致该问题,测试代码如下:
NSMutableArray *mArray = [NSMutableArray arrayWithArray:@[@0, @1, @2]];
for (NSNumber *num in mArray) {
[mArray addObject:@3];
}
运行后控制台输出日志:
*** Terminating app due to uncaught exception 'NSGenericException',
reason: '*** Collection <__NSArrayM: 0x600000c08660> was mutated while being enumerated.'
- NSMallocException(内存分配异常)
这类异常的主要原因是无法分配足够的内存空间。例如,分配一块超大的内存空间就会导致此类的异常,测试代码如下:
NSMutableData *mData = [[NSMutableData alloc] initWithCapacity:1];
NSUInteger len = 1844674407370955161;
[mData increaseLengthBy:len];
运行后控制台输出日志:
*** Terminating app due to uncaught exception 'NSMallocException',
reason: 'Failed to grow buffer'
- NSFileHandleOperationException(文件处理异常)
这类异常的主要原因是对文件进行相关操作时产生了异常,如手机没有足够的存储空间,文件读写权限问题等。例如,对于一个只有读权限的文件进行写操作,测试代码如下:
NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [cacheDir stringByAppendingPathComponent:@"1.txt"];
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSString *str1 = @"Hello1";
NSData *data1 = [str1 dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:data1 attributes:nil];
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
[fileHandle seekToEndOfFile];
NSString *str2 = @"Hello2";
NSData *data2 = [str2 dataUsingEncoding:NSUTF8StringEncoding];
[fileHandle writeData:data2];
[fileHandle closeFile];
运行后控制台输出日志:
*** Terminating app due to uncaught exception 'NSFileHandleOperationException',
reason: '*** -[NSConcreteFileHandle writeData:]: Bad file descriptor'
以上介绍了几个常见的Objective-C异常,接下来我们来看下如何捕获Objective-C异常。
####2.1.2 捕获Objective-C异常