UncaughtExceptionHandler 捕捉异常

今天在看我们线上的bug的时候 ,突然想到是否可以直接在出现 bug 和 异常的时候给出一个提醒啦,愕然一看发现老早就有人做了这工作啦 UncaughtExceptionHandler ,于是去看了下里面的实现,特此记录学习下。

再此之前,还是先回顾下基本的NSException

  • Exception关键字:
@try
{
// 可能会抛出异常的代码
}
@catch(NSException* exception)
{
// 异常的处理代码
}
@finally
{
// 不论是否有异常,总是会被执行的代码,通常用于 clean
}
  • NSException实例:
// 先创建一个异常对象
NSExcetpion * exception = [NSException exceptionWithName:...];
// 然后可以通过 
@throw exception; // 将其抛出 
或
[exception raise]; // 发送异常消息

平常我们直接在代码中用的最多还是,@try -- @catch -- @finally, 另外 NSExcetpion 还可以对其进行Custom,继承并扩展使用它。具体实例:Objective-C - 异常处理(NSException)


UncaughtExceptionHandler

UncaughtExceptionHandler 里面是利用 iOS SDK中提供了一个现成的函数NSSetUncaughtExceptionHandler 用来做异常处理的,通过抛出的Signal,专门对Signal处理。

void InstallUncaughtExceptionHandler(void) {
    NSSetUncaughtExceptionHandler(&HandleException);
    signal(SIGABRT, SignalHandler);
    signal(SIGILL, SignalHandler);
    signal(SIGSEGV, SignalHandler);
    signal(SIGFPE, SignalHandler);
    signal(SIGBUS, SignalHandler);
    signal(SIGPIPE, SignalHandler);
}

注意不同情况的 signal , 这等同于不同情况的异常。

void HandleException(NSException *exception)
{
    // 递增的一个全局计数器,很快很安全,防止并发数太大
    int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
    if (exceptionCount > UncaughtExceptionMaximum) return;
    // 获取 堆栈信息的数组
    NSArray *callStack = [UncaughtExceptionHandler backtrace];
    // 设置该字典
    NSMutableDictionary *userInfo =
    [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
    // 给 堆栈信息 设置 地址 Key
    [userInfo
     setObject:callStack
     forKey:UncaughtExceptionHandlerAddressesKey];

    // 假如崩溃了执行 handleException: ,并且传出 NSException
    [[[UncaughtExceptionHandler alloc] init] 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;
    // 设置是哪一种 single 引起的问题
    NSMutableDictionary *userInfo =
    [NSMutableDictionary
     dictionaryWithObject:[NSNumber numberWithInt:signal]
     forKey:UncaughtExceptionHandlerSignalKey];
    // 获取堆栈信息数组
    NSArray *callStack = [UncaughtExceptionHandler backtrace];
    // 写入地址
    [userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
    //  假如崩溃了执行 handleException: ,并且传出 NSException
    [[[UncaughtExceptionHandler alloc] init] 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)
{
    // 递增的一个全局计数器,很快很安全,防止并发数太大
    int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
    if (exceptionCount > UncaughtExceptionMaximum) return;
    // 获取 堆栈信息的数组
    NSArray *callStack = [UncaughtExceptionHandler backtrace];
    // 设置该字典
    NSMutableDictionary *userInfo =
    [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
    // 给 堆栈信息 设置 地址 Key
    [userInfo
     setObject:callStack
     forKey:UncaughtExceptionHandlerAddressesKey];

    // 假如崩溃了执行 handleException: ,并且传出 NSException
    [[[UncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:) withObject:[NSException exceptionWithName:[exception name] reason:[exception reason]
      userInfo:userInfo]
     waitUntilDone:YES];
}

此处注意OSAtomicIncrement32的使用,它此处是一个递增的一个全局计数器,效果又快又安全,是为了防止并发数太大出现错误的情况。

+ (NSArray *)backtrace
{
    void* callstack[128];
    //  该函数用来获取当前线程调用堆栈的信息,获取的信息将会被存放在buffer中(callstack),它是一个指针数组。
    int frames = backtrace(callstack, 128);
    //  backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组.
    char **strs = backtrace_symbols(callstack, frames);
    NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
    for (
         int i = UncaughtExceptionHandlerSkipAddressCount;
         i < UncaughtExceptionHandlerSkipAddressCount + UncaughtExceptionHandlerReportAddressCount;
         i++)
    {
        [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
    }
    free(strs); // 记得free
    return backtrace;
}

这边值得注意的是下面这两个函数方法

int backtrace(void**,int) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
char** backtrace_symbols(void* const*,int) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

该函数用来获取当前线程调用堆栈的信息,并且转化为字符串数组。

最后再来处理,此处涉及到 CFRunLoopRunInModekill 值得注意!

- (void)handleException:(NSException *)exception
{
     // 打印或弹出框
     // TODO :
          
    // 接到程序崩溃时的信号进行自主处理
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
    while (!dismissed)
    {
        for (NSString *mode in (NSArray *)allModes) {
            CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
        }
    }
    CFRelease(allModes);

    // 下面等同于清空之前设置的
    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];
    }
}

话说回来,这个UncaughtExceptionHandler 代码量不多,但真的涉及到东西好多,好多都可以深入细扣,如有兴趣和时间可以慢慢深入研究源码,以及源码背后的东东。

当然不足的是,并不是所有的程序崩溃都是能可以捕捉到异常的,有些时候是因为内存等一些其他的错误导致程序的崩溃,这样的情况就不能通过这个方法直接获取到啦。 同时Apple并不建议我们抛出异常,耗费的资源太大,所以此处作为了解学习比较适宜。

你可能感兴趣的:(UncaughtExceptionHandler 捕捉异常)