Tip-Crash分析

425 阅读2分钟

奔溃错误主动捕捉

  • 先贴上一段源码
//  LGUncaughtExceptionHandle.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface LGUncaughtExceptionHandle : NSObject

@property (nonatomic) BOOL dismissed;

+ (void)installUncaughtSignalExceptionHandler;

@end

NS_ASSUME_NONNULL_END

#import "LGUncaughtExceptionHandle.h"

#import <SCLAlertView.h>

#import <UIKit/UIKit.h>

#include <libkern/OSAtomic.h>

#include <execinfo.h>

#include <stdatomic.h>

NSString * const LGUncaughtExceptionHandlerSignalExceptionName = @"LGUncaughtExceptionHandlerSignalExceptionName";

NSString * const LGUncaughtExceptionHandlerSignalExceptionReason = @"LGUncaughtExceptionHandlerSignalExceptionReason";

NSString * const LGUncaughtExceptionHandlerSignalKey = @"LGUncaughtExceptionHandlerSignalKey";

NSString * const LGUncaughtExceptionHandlerAddressesKey = @"LGUncaughtExceptionHandlerAddressesKey";

NSString * const LGUncaughtExceptionHandlerFileKey = @"LGUncaughtExceptionHandlerFileKey";

NSString * const LGUncaughtExceptionHandlerCallStackSymbolsKey = @"LGUncaughtExceptionHandlerCallStackSymbolsKey";

atomic_int      LGUncaughtExceptionCount = 0;

const int32_t   LGUncaughtExceptionMaximum = 8;

const NSInteger LGUncaughtExceptionHandlerSkipAddressCount = 4;

const NSInteger LGUncaughtExceptionHandlerReportAddressCount = 5;

@implementation LGUncaughtExceptionHandle

/// Exception

void LGExceptionHandlers(NSException *exception) {

    NSLog(@"%s",__func__);

    

    int32_t exceptionCount = atomic_fetch_add_explicit(&LGUncaughtExceptionCount,1,memory_order_relaxed);

    if (exceptionCount > LGUncaughtExceptionMaximum) {

        return;

    }

    // 获取堆栈信息 - model 编程思想

    NSArray *callStack = [LGUncaughtExceptionHandle lg_backtrace];

    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];

    [userInfo setObject:exception.name forKey:LGUncaughtExceptionHandlerSignalExceptionName];

    [userInfo setObject:exception.reason forKey:LGUncaughtExceptionHandlerSignalExceptionReason];

    [userInfo setObject:callStack forKey:LGUncaughtExceptionHandlerAddressesKey];

    [userInfo setObject:exception.callStackSymbols forKey:LGUncaughtExceptionHandlerCallStackSymbolsKey];

    [userInfo setObject:@"LGException" forKey:LGUncaughtExceptionHandlerFileKey];

    

    [[[LGUncaughtExceptionHandle alloc] init]

     performSelectorOnMainThread:@selector(lg_handleException:)

     withObject:

     [NSException

      exceptionWithName:[exception name]

      reason:[exception reason]

      userInfo:userInfo]

     waitUntilDone:YES];

    

}

+ (void)installUncaughtSignalExceptionHandler{

    // uncaught_handler() = fn = LGExceptionHandlers

//  objc_setUncaughtExceptionHandler()

    NSSetUncaughtExceptionHandler(&LGExceptionHandlers);

}

- (void)lg_handleException:(NSException *)exception{

    // 保存上传服务器

    

    NSDictionary *userinfo = [exception userInfo];

    [self saveCrash:exception file:[userinfo objectForKey:LGUncaughtExceptionHandlerFileKey]];

    

    SCLAlertView *alert = [[SCLAlertView alloc] initWithNewWindowWidth:300.f];

    [alert addButton:@"奔溃" actionBlock:^{

        self.dismissed = YES;

    }];

    [alert showSuccess:exception.name subTitle:exception.reason closeButtonTitle:nil duration:0.0f];

}

/// 保存奔溃信息或者上传

- (void)saveCrash:(NSException *)exception file:(NSString *)file{

    

    NSArray *stackArray = [[exception userInfo] objectForKey:LGUncaughtExceptionHandlerCallStackSymbolsKey];// 异常的堆栈信息

    NSString *reason = [exception reason];// 出现异常的原因

    NSString *name = [exception name];// 异常名称

    

    // 或者直接用代码,输入这个崩溃信息,以便在console中进一步分析错误原因

    // NSLog(@"crash: %@", exception);

    

    NSString * _libPath  = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:file];

    

    if (![[NSFileManager defaultManager] fileExistsAtPath:_libPath]){

        [[NSFileManager defaultManager] createDirectoryAtPath:_libPath withIntermediateDirectories:YES attributes:nil error:nil];

    }

    

    NSDate *dat = [NSDate dateWithTimeIntervalSinceNow:0];

    NSTimeInterval a=[dat timeIntervalSince1970];

    NSString *timeString = [NSString stringWithFormat:@"%f", a];

    

    NSString * savePath = [_libPath stringByAppendingFormat:@"/error%@.log",timeString];

    

    NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];

    

    BOOL sucess = [exceptionInfo writeToFile:savePath atomically:YES encoding:NSUTF8StringEncoding error:nil];

    

    NSLog(@"保存崩溃日志 sucess:%d,%@",sucess,savePath);

    

}

/// 获取函数堆栈信息

+ (NSArray *)lg_backtrace{

    

    void* callstack[128];

    int frames = backtrace(callstack, 128);//用于获取当前线程的函数调用堆栈,返回实际获取的指针个数

    char **strs = backtrace_symbols(callstack, frames);//从backtrace函数获取的信息转化为一个字符串数组

    int i;

    NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];

    for (i = LGUncaughtExceptionHandlerSkipAddressCount;

         i < LGUncaughtExceptionHandlerSkipAddressCount+LGUncaughtExceptionHandlerReportAddressCount;

         i++)

    {

        [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];

    }

    free(strs);

    return backtrace;

}

@end

  • 使用场景:
    • 当一个数组只有5个元素时,点击按钮取下标为5的元素,必然会报错且系统奔溃,因为下标最大为4.
    • 所以我需要收集错误当系统不崩溃。
- (void)viewDidLoad {

    [super viewDidLoad];
    self.dataArray = @[@"Hank",@"CC",@"Kody",@"Cooci",@"Cat"];

}

- (IBAction)exceptionAction:(id)sender {

    NSLog(@"%@",self.dataArray[5]);

}
  • 使用:在app进入这个方法就调用[LGUncaughtExceptionHandle installUncaughtSignalExceptionHandler];
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    [LGUncaughtExceptionHandle installUncaughtSignalExceptionHandler];

    return YES;

}
  • 在这个地方打上断点后点击按钮,就可以主动捕捉到错误信息了。 image.png