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

来源:互联网 发布:淘宝对3c认证的处罚 编辑:程序博客网 时间:2024/06/16 02:40

1.前言

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

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

2.创建单利

单利主要做有一下功能,捕获崩溃日志、存取日子和界面提示功能;详见ZFJUncaughtExceptionHandler.h的方法;

#import <Foundation/Foundation.h>//返回地址路径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













0 0
原创粉丝点击