开发iOS应用,解决Crash问题始终是一个难题。Crash分为两种,一种是由EXC_BAD_ACCESS引起的,原因是访问了不属于本进程的内存地址,有可能是访问已被释放的内存;另一种是未被捕获的Objective-C异常(NSException),导致程序向自身发送了SIGABRT信号而崩溃。其实对于未捕获的Objective-C异常,我们是有办法将它记录下来的,如果日志记录得当,能够解决绝大部分崩溃的问题。这里对于UI线程与后台线程分别说明。
网上的捕获异常的方法都大同小异,都是处理处理signal信号。这里我把捕获的异常保存到缓存目录上,然后你可以通过邮件或者网络接口把自己发给自己或者公司,便于自己分析和在下个版本上进行修复工作。
单利主要做有一下功能,捕获崩溃日志、存取日子和界面提示功能;详见ZFJUncaughtExceptionHandler.h的方法;
#import
//返回地址路径
typedef void(^ logPathBlock)(NSString *pathStr);
@interface ZFJUncaughtExceptionHandler : NSObject
+ (instancetype)shareInstance;
@property (nonatomic,copy) logPathBlock pathBlock;
//是否显示错误提示框 默认是不显示的
@property (nonatomic, copy) ZFJUncaughtExceptionHandler*(^showAlert)(BOOL yesOrNo);
//是否显示错误信息
@property (nonatomic, copy) ZFJUncaughtExceptionHandler*(^showErrorInfor)(BOOL yesOrNo);
//回调返回错误日志
@property (nonatomic, copy) ZFJUncaughtExceptionHandler*(^getlogPathBlock)(void(^ logPathBlock)(NSString *pathStr));
//错误日志路径
@property (nonatomic,strong) NSString *logFilePath;
ZFJUncaughtExceptionHandler * InstanceZFJUncaughtExceptionHandler(void);
@end
初始化+ (instancetype)shareInstance{
static ZFJUncaughtExceptionHandler *_manager = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_manager = [[self alloc] init];
[_manager uiConfig];
});
return _manager;
}
#pragma mark - 设置日志存取的路径
- (void)uiConfig{
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [docPath stringByAppendingPathComponent:@"ZFJUncaughtExceptionHandlerLog.txt"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:filePath]) {
[fileManager createFileAtPath:filePath contents:[@"~~~~~~~~~~~~~~~~~~程序异常日志~~~~~~~~~~~~~~~~~~\n\n" dataUsingEncoding:NSUTF8StringEncoding] attributes:nil];
}
self.logFilePath = filePath;
}
默认我是打开异常捕获的,当然你可以这这里面添加一个字段来控制打开或者关闭捕获功能;ZFJUncaughtExceptionHandler *InstanceZFJUncaughtExceptionHandler(void){
NSSetUncaughtExceptionHandler(&HandleException);
signal(SIGABRT, SignalHandler);
signal(SIGILL, SignalHandler);
signal(SIGSEGV, SignalHandler);
signal(SIGFPE, SignalHandler);
signal(SIGBUS, SignalHandler);
signal(SIGPIPE, SignalHandler);
return [ZFJUncaughtExceptionHandler shareInstance];
}
可以通过添加一个字段install来打开或者关闭,例如:
NSSetUncaughtExceptionHandler(install ? HandleException : NULL);
signal(SIGABRT, install ? SignalHandler : SIG_DFL);
signal(SIGILL, install ? SignalHandler : SIG_DFL);
signal(SIGSEGV, install ? SignalHandler : SIG_DFL);
signal(SIGFPE, install ? SignalHandler : SIG_DFL);
signal(SIGBUS, install ? SignalHandler : SIG_DFL);
signal(SIGPIPE, install ? SignalHandler : SIG_DFL);
从异常堆栈里面获取异常信息;void HandleException(NSException *exception){
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
//如果太多不用处理
if (exceptionCount > UncaughtExceptionMaximum) {
return;
}
//获取调用堆栈
NSArray *callStack = [exception callStackSymbols];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
//在主线程中,执行制定的方法, withObject是执行方法传入的参数
[[ZFJUncaughtExceptionHandler shareInstance] performSelectorOnMainThread:@selector(handleException:) withObject:[NSException exceptionWithName:[exception name] reason:[exception reason] userInfo:userInfo] waitUntilDone:YES];
}
void SignalHandler (int signal){
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum) {
return;
}
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey];
NSArray *callStack = [ZFJUncaughtExceptionHandler backtrace];
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
[[ZFJUncaughtExceptionHandler shareInstance] performSelectorOnMainThread:@selector(handleException:) withObject: [NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName reason: [NSString stringWithFormat: NSLocalizedString(@"Signal %d was raised.", nil), signal] userInfo: [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey]] waitUntilDone:YES];
}
处理异常- (void)handleException:(NSException *)exception{
//保存日志 可以发送日志到自己的服务器上
[self validateAndSaveCriticalApplicationData:exception];
NSString *_erroeMeg = nil;
NSString *userInfo = [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey];
if(self.isShowErrorInfor){
_erroeMeg = [NSString stringWithFormat:NSLocalizedString(@"如果点击继续,程序有可能会出现其他的问题,建议您还是点击退出按钮并重新打开\n" @"异常原因如下:\n%@\n%@", nil), [exception reason], userInfo];
}else{
_erroeMeg = [NSString stringWithFormat:NSLocalizedString(@"如果点击继续,程序有可能会出现其他的问题,建议您还是点击退出按钮并重新打开", nil)];
}
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"抱歉,程序出现了异常" message:_erroeMeg delegate:self cancelButtonTitle:@"退出" otherButtonTitles:@"继续", nil];
dispatch_async(dispatch_get_main_queue(), ^{
if(_isShowAlert){
[alert show];
}
});
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!_dismissed){
for (NSString *mode in (__bridge NSArray *)allModes) {
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
CFRelease(allModes);
#pragma clang diagnostic pop
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];
}
}
异常日志保存到本地;#pragma mark - 保存错误信息日志
- (void)validateAndSaveCriticalApplicationData:(NSException *)exception{
NSString *exceptionMessage = [NSString stringWithFormat:NSLocalizedString(@"\n******************** %@ 异常原因如下: ********************\n%@\n%@\n==================== End ====================\n\n", nil), [self currentTimeString], [exception reason], [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]];
NSFileHandle *handle = [NSFileHandle fileHandleForUpdatingAtPath:self.logFilePath];
[handle seekToEndOfFile];
[handle writeData:[exceptionMessage dataUsingEncoding:NSUTF8StringEncoding]];
[handle closeFile];
if(self.pathBlock){
self.pathBlock(self.logFilePath);
}
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//简单调用
//InstanceZFJUncaughtExceptionHandler();
//链式调用 是否显示警告框 是否显示错误信息 是否回调日志地址
InstanceZFJUncaughtExceptionHandler().showAlert(YES).showErrorInfor(YES).getlogPathBlock(^(NSString *logPathStr){
NSLog(@"程序异常日志地址 == %@",logPathStr);
});
//当然我们也可以直接直接获取日志文件地址
//NSString *logFilePath = InstanceZFJUncaughtExceptionHandler().logFilePath;
return YES;
}
点击下载
http://download.csdn.net/detail/u014220518/9705941