iOS-iOS异常捕获和崩溃日志

1.前言

开发iOS应用,解决Crash问题始终是一个难题。Crash分为两种,一种是由EXC_BAD_ACCESS引起的,原因是访问了不属于本进程的内存地址,有可能是访问已被释放的内存;另一种是未被捕获的Objective-C异常(NSException),导致程序向自身发送了SIGABRT信号而崩溃。其实对于未捕获的Objective-C异常,我们是有办法将它记录下来的,如果日志记录得当,能够解决绝大部分崩溃的问题。这里对于UI线程与后台线程分别说明。

网上的捕获异常的方法都大同小异,都是处理处理signal信号。这里我把捕获的异常保存到缓存目录上,然后你可以通过邮件或者网络接口把自己发给自己或者公司,便于自己分析和在下个版本上进行修复工作。

2.创建单利

单利主要做有一下功能,捕获崩溃日志、存取日子和界面提示功能;详见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);
    }
}

3.函数调用

- (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;
}

4.运行效果

5.DEMO下载

点击下载

http://download.csdn.net/detail/u014220518/9705941













你可能感兴趣的:(iOS开发)