iOS程序异常Crash友好化处理


       前两天接到个面试,面试官问到上线的app怎么避免闪退,首先想到的就是在编码的时候进行各种容错,但貌似并不是面试官想要的答案,所以表现的很糟糕。今天有时间就来整理一下,希望有所帮助。实现效果如图:

carsh.gif

效果实现:

demo截图

用法:

1.将截图的中

1
2.在```AppDelegate.m```中找到以下方法并如下添加代码:

  • (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions {
    // Override point for customization after application launch.

    [UncaughtExceptionHandler installUncaughtExceptionHandler:YES showAlert:YES];
    return YES;
    }

    1
    2
    3
    以上代码就可以实现稍微友好一点的crash拦截处理。
    ## 代码解释:
    ```UncaughtExceptionHandler.h```主要代码:

#import <Foundation/Foundation.h>

#import <UIKit/UIKit.h>

@interface UncaughtExceptionHandler : NSObject

/*!

  • 异常的处理方法
    *
  • @param install 是否开启捕获异常
  • @param showAlert 是否在发生异常时弹出alertView
    */
    • (void)installUncaughtExceptionHandler:(BOOL)install showAlert:(BOOL)showAlert;
      @end
      1
      2
      ```UncaughtExceptionHandler.m```文件主要的代码如下:
      #### 1.发送异常信号

/*

  • 异常的处理方法
    *
  • @param install 是否开启捕获异常
  • @param showAlert 是否在发生异常时弹出alertView
    */

    • (void)installUncaughtExceptionHandler:(BOOL)install showAlert:(BOOL)showAlert {

    if (install && showAlert) {

    [[self alloc] alertView:showAlert];
    

    }

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

    1
    2
    3
    4
     产生上述的signal的时候就会调用我们定义的```SignalHandler```来处理异常。
    > ps: NSSetUncaughtExceptionHandler就是iOS SDK中提供的一个现成的函数,用来捕获异常的方法,使用方便。但它不能捕获抛出的signal,所以定义了SignalHandler方法。

    #### 2.处理异常

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是执行方法传入的参数
[[[UncaughtExceptionHandler alloc] init]
 performSelectorOnMainThread:@selector(handleException:)
 withObject:
 [NSException exceptionWithName:[exception name]
                         reason:[exception reason]
                       userInfo:userInfo]
 waitUntilDone:YES];

}

1
2
该方法就是对应```NSSetUncaughtExceptionHandler```的处理,只要方法关联到这个函数,那么发生相应错误时会自动调用该函数,调用时会传入```exception```参数。获取异常后会将捕获的异常传入最终调用处理的```handleException```函数。
#### 3.无法捕获的signal处理

//处理signal报错
void SignalHandler(int signal) {

int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
// 如果太多不用处理
if (exceptionCount > UncaughtExceptionMaximum) {
    return;
}

NSString* description = nil;
switch (signal) {
    case SIGABRT:
        description = [NSString stringWithFormat:@"Signal SIGABRT was raised!\n"];
        break;
    case SIGILL:
        description = [NSString stringWithFormat:@"Signal SIGILL was raised!\n"];
        break;
    case SIGSEGV:
        description = [NSString stringWithFormat:@"Signal SIGSEGV was raised!\n"];
        break;
    case SIGFPE:
        description = [NSString stringWithFormat:@"Signal SIGFPE was raised!\n"];
        break;
    case SIGBUS:
        description = [NSString stringWithFormat:@"Signal SIGBUS was raised!\n"];
        break;
    case SIGPIPE:
        description = [NSString stringWithFormat:@"Signal SIGPIPE was raised!\n"];
        break;
    default:
        description = [NSString stringWithFormat:@"Signal %d was raised!",signal];
}

NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
NSArray *callStack = [UncaughtExceptionHandler backtrace];
[userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey];
[userInfo setObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey];

//在主线程中,执行指定的方法, withObject是执行方法传入的参数
[[[UncaughtExceptionHandler alloc] init]
 performSelectorOnMainThread:@selector(handleException:)
 withObject:
 [NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName
                         reason: description
                       userInfo: userInfo]
 waitUntilDone:YES];

}

1
2
以上方法是对于捕获不到的signal信号进行处理,列出常见的异常类型。
#### 4.堆栈调用

//获取调用堆栈

  • (NSArray *)backtrace {

    //指针列表
    void callstack[128];
    //backtrace用来获取当前线程的调用堆栈,获取的信息存放在这里的callstack中
    //128用来指定当前的buffer中可以保存多少个void
    元素
    //返回值是实际获取的指针个数
    int frames = backtrace(callstack, 128);
    //backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组
    //返回一个指向字符串数组的指针
    //每个字符串包含了一个相对于callstack中对应元素的可打印信息,包括函数名、偏移地址、实际返回地址
    char **strs = backtrace_symbols(callstack, frames);

    int i;
    NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
    for (i = 0; i < frames; i++) {

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

    }
    free(strs);

    return backtrace;
    }

    1
    2
    > backtrace是Linux下用来追踪函数调用堆栈以及定位段错误的函数。
    #### 5.使用UIAlerView进行友好化提示
  • (void)handleException:(NSException *)exception {

    [self validateAndSaveCriticalApplicationData:exception];

    if (!showAlertView) {

    return;
    

    }

#pragma clang diagnostic push

#pragma clang diagnostic ignored “-Wdeprecated-declarations”
UIAlertView *alert =
[[UIAlertView alloc]
initWithTitle:@”出错啦”
message:[NSString stringWithFormat:@”你可以尝试继续操作,但是应用可能无法正常运行.\n”]
delegate:self
cancelButtonTitle:@”退出”
otherButtonTitles:@”继续”, nil];
[alert show];

#pragma clang diagnostic pop

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);

while (!self.dismissed) {
    //点击继续
    for (NSString *mode in (__bridge NSArray *)allModes) {
        //快速切换Mode
        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];
}

}
`

在这里你可以做自己的crash收集操作,例如上传服务器等。

最后,我想说对网上给的这个拦截处理个人理解的不是很透彻,借鉴了多位大牛的技术分享,如果有新的解决方案,麻烦评论告诉我,大家一起共同进步。本文已同步至本人博客

代码传送门
更多文章

-------------本文结束感谢您的阅读-------------
JackerooChu wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持技术积累和分享,您的支持将鼓励我继续创作!
  • 本文作者: JackerooChu
  • 本文链接: http://chuzhaozhi.cn/1258.html
  • 版权声明: 本博客所有文章除特别声明外,版权归JackerooChu所有,转载请注明出处!
分享到: