iOS性能优化 — 一、crash监控及防崩溃处理 - 图1
**
大家好,欢迎来到 iOS性能优化
本篇文章将为大家讲解下crash监控及防崩溃处理。

  • 如何收集crash
    • 利用bugly、友盟等第三方收集
    • 监控crash原理
  • 防崩溃处理
    • 常见崩溃类型
    • 防崩溃处理方案
      • hook方案
      • 安全接口

        如何收集crash

        在平常开发过程中,由于代码的不严谨比如不对入参做校验,使用C++野指针等会造成程序crash。crash应该算是最严重的bug了,尤其是线上crash,如果App用户量大的话可能造成很大的影响,所以需要有一套机制来收集项目中的crash并及时解决。

        利用bugly、友盟等第三方收集

        大部分公司都是采用第三方平台来收集crash。业内用的比较多有bugly、友盟、talkingdata。笔者比较推荐bugly,腾讯研发,比较轻量,用来监控crash和卡顿还是很方便的。

        监控crash原理

        一线大厂,大部分都会自研crash捕获框架。这个时候了解crash捕获原理就很有必要了,大家可以阅读开源库kscrash或者plcrashreporter其实捕获crash原理很简单。主要需要处理两种情况:
        1、OC类异常。NSException异常是OC代码导致的crash。我们可以先通过NSGetUncaughtExceptionHandler保存先前注册的异常处理器,然后通过NSSetUncaughtExceptionHandler设置我们自己的异常处理器,我们不监控了,需要再设置回原理的异常处理器,在我们自己的uncaughtHandleException处理器里面,需要手动调用下原来的处理器
        1. static NSUncaughtExceptionHandler* g_previousUncaughtExceptionHandler;
        2. void installUncaughtExceptionHandler(void){
        3. g_previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
        4. NSSetUncaughtExceptionHandler(&uncaughtHandleException);
        5. }
        6. void uninstallUncaughtExceptionHandler(void){
        7. if(g_previousUncaughtExceptionHandler){
        8. NSSetUncaughtExceptionHandler(g_previousUncaughtExceptionHandler);
        9. }
        10. }
        11. void uncaughtHandleException(NSException *exception)
        12. {
        13. // 异常的堆栈信息
        14. NSArray *stackArray = [exception callStackSymbols];
        15. // 出现异常的原因
        16. NSString *reason = [exception reason];
        17. // 异常名称
        18. NSString *name = [exception name];
        19. NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];
        20. NSLog(exceptionInfo);
        21. if (g_previousUncaughtExceptionHandler != NULL)
        22. {
        23. g_previousUncaughtExceptionHandler(exception);
        24. }
        25. }
        2、Signal信号捕获。Signal信号是由iOS底层mach信号异常转换后以signal信号抛出的异常。既然是兼容posix标准的异常,我们同样可以通过sigaction函数注册对应的信号。
        1. static struct sigaction* g_previousSignalHandlers = NULL; //旧的信号处理函数结构体数组
        2. static int g_fatalSignals[] = {
        3. SIGHUP,
        4. SIGINT,
        5. SIGQUIT,
        6. SIGABRT,
        7. SIGILL,
        8. SIGSEGV,
        9. SIGFPE,
        10. SIGBUS,
        11. SIGPIPE
        12. };
        13. static int g_fatalSignalsCount = (sizeof(g_fatalSignals) / sizeof(g_fatalSignals[0]));
        14. const int* kssignal_fatalSignals(void){
        15. return g_fatalSignals;
        16. }
        17. int kssignal_numFatalSignals(void){
        18. return g_fatalSignalsCount;
        19. }
        20. void signalExceptionHandler(int signo, siginfo_t *info, void *uapVoid){
        21. void *frames[128];
        22. int i, len = backtrace(frames, 128);
        23. //堆栈信息
        24. char **symbols = backtrace_symbols(frames, len);
        25. NSMutableString *exceptionContent = [[NSMutableString alloc] init];
        26. [exceptionContent appendFormat:@"signal name:%d \n signal stack:\n",signo];
        27. for (i = 0; i < len; ++i)
        28. {
        29. [exceptionContent appendFormat:@"%s\r\n", symbols[i]];
        30. }
        31. //释放缓存
        32. free(symbols);
        33. raise(signo);
        34. }
        35. void installSignalHandler(void){
        36. const int* fatalSignals = kssignal_fatalSignals();
        37. int fatalSignalsCount = kssignal_numFatalSignals();
        38. if(g_previousSignalHandlers == NULL){
        39. g_previousSignalHandlers = (struct sigaction *)malloc(sizeof(*g_previousSignalHandlers)
        40. * (unsigned)fatalSignalsCount);
        41. }
        42. //初始化处理函数结构体
        43. struct sigaction action = {{0}};
        44. action.sa_flags = SA_SIGINFO | SA_ONSTACK;
        45. sigemptyset(&action.sa_mask);
        46. action.sa_sigaction = &signalExceptionHandler;
        47. for(int i = 0; i < fatalSignalsCount; i++)
        48. {
        49. if(sigaction(fatalSignals[i], &action, &g_previousSignalHandlers[i]) != 0)
        50. {
        51. // 取消已监听的handler
        52. for(i--;i >= 0; i--)
        53. {
        54. sigaction(fatalSignals[i], &g_previousSignalHandlers[i], NULL);
        55. }
        56. break;
        57. }
        58. }
        59. }
        60. void uninstallSignalHandler(void){
        61. const int* fatalSignals = kssignal_fatalSignals();
        62. int fatalSignalsCount = kssignal_numFatalSignals();
        63. for(int i = 0; i < fatalSignalsCount; i++)
        64. {
        65. sigaction(fatalSignals[i], &g_previousSignalHandlers[i], NULL);
        66. }
        67. }

        防崩溃处理

        常见崩溃类型

        根据笔者经验来看,oc中大部分崩溃都是源于没有对调用方法进行入参判断,比如数组添加object没有判空,访问数组元素越界等;还有一些C++崩溃,比如使用野指针。

        防崩溃处理方案

        由于oc中大部分崩溃都是来源于未对入参进行判断,所以调用方法对入参进行判断就能解决崩溃。如何统一地解决这类崩溃,有两种方案:hook方案和安全接口

        hook方案

        该方案对系统常见类的方法进行hook,进行入参判断。比如对hook NSMutableArray的addObject方法,进行判空操作。
        ``` @implementation NSMutableArray (akSafe)
  • (void)load { [self swizzMethodOriginalSelector:@selector(addObject:)
    1. swizzledSelector:@selector(akSafe_addObject:)];
    }
  • (void)swizzMethodOriginalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector { Method originalMethod = class_getInstanceMethod(self.class, originalSelector); Method swizzledMethod = class_getInstanceMethod(self.class, swizzledSelector); BOOL didAddMethod = class_addMethod(self.class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) {
    1. class_replaceMethod(self.class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
    1. method_exchangeImplementations(originalMethod, swizzledMethod);
    } }
  • (void)aksafe_AddObject:(id)anObject { if (anObject) {
    1. [self aksafe_AddObject:anObject];
    } } @end
    1. <a name="1iM4c"></a>
    2. ### 安全接口
    3. 该方案对系统常见类的方法进行一层封装,进行入参判断。大家统一调用安全接口,比如封装NSMutableArray的addObject方法为aksafe_AddObject,大家统一调用aksafe_AddObject添加对象。<br />
    @implementation NSMutableArray (aksafe)
  • (void)aksafe_AddObject:(id)anObject { if (anObject) {
    1. [self addObject:anObject];
    } } @end ``` 两种方案各有优缺点,hook方案优点是业务方直接调用系统方法就行,缺点是由于要进行hook,有损性能;安全接口方案是业务方要统一调用安全接口,优点则是轻量。笔者推荐方案二,轻量并且可以作为编码规范。

    资料推荐

    如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群1012951431来获取一份详细的大厂面试资料为你的跳槽多添一份保障。
    iOS性能优化 — 一、crash监控及防崩溃处理 - 图2