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并不建议我们抛出异常,耗费的资源太大,所以此处作为了解学习比较适宜。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • 程序都会有bug,那么在有bug和异常时能不能给个提醒呢,今天咱们来看看这个问题怎么解决 首先说下基本的NSExc...
    爱吃鱼的小灰阅读 680评论 1 9
  • 转载:http://www.cnblogs.com/lulipro/p/7504267.html 一、异常简介 程...
    SinX竟然被占用了阅读 972评论 2 2
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,505评论 18 399
  • 最近项目上需要对崩溃信息进行处理,要满足崩溃后及时捕捉到崩溃信息,当应用下次打开后再将报文上传至服务器...
    迷失之刃阅读 4,626评论 9 8
  • 1.当好跟班 女人最喜欢的就是买买买,逛商场时如果丈夫能陪同,而且随时从妻子手里接过来买好的东西,妻子会感觉很自豪...
    鱼笨自由阅读 2,706评论 0 7