浅谈iOS性能优化之APP崩溃与hook方案
本篇文章将为大家讲解下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处理器里面,需要手动调用下原来的处理器。
static NSUncaughtExceptionHandler* g_previousUncaughtExceptionHandler;
void installUncaughtExceptionHandler(void){g_previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();NSSetUncaughtExceptionHandler(&uncaughtHandleException);
}
void uninstallUncaughtExceptionHandler(void){if(g_previousUncaughtExceptionHandler){NSSetUncaughtExceptionHandler(g_previousUncaughtExceptionHandler);}
}
void uncaughtHandleException(NSException *exception)
{// 异常的堆栈信息NSArray *stackArray = [exception callStackSymbols];// 出现异常的原因NSString *reason = [exception reason];// 异常名称NSString *name = [exception name];NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];NSLog(exceptionInfo);if (g_previousUncaughtExceptionHandler != NULL){g_previousUncaughtExceptionHandler(exception);}
} 2、Signal信号捕获。Signal信号是由iOS底层mach信号异常转换后以signal信号抛出的异常。既然是兼容posix标准的异常,我们同样可以通过sigaction函数注册对应的信号。
static struct sigaction* g_previousSignalHandlers = NULL; //旧的信号处理函数结构体数组
static int g_fatalSignals[] = {SIGHUP,SIGINT,SIGQUIT,SIGABRT,SIGILL,SIGSEGV,SIGFPE,SIGBUS,SIGPIPE
};
static int g_fatalSignalsCount = (sizeof(g_fatalSignals) / sizeof(g_fatalSignals[0]));
const int* kssignal_fatalSignals(void){return g_fatalSignals;
}
int kssignal_numFatalSignals(void){return g_fatalSignalsCount;
}
void signalExceptionHandler(int signo, siginfo_t *info, void *uapVoid){void *frames[128];int i, len = backtrace(frames, 128);//堆栈信息char **symbols = backtrace_symbols(frames, len);NSMutableString *exceptionContent = [[NSMutableString alloc] init];[exceptionContent appendFormat:@"signal name:%d \n signal stack:\n",signo];for (i = 0; i < len; ++i){[exceptionContent appendFormat:@"%s\r\n", symbols[i]];}//释放缓存free(symbols);raise(signo);
}
void installSignalHandler(void){const int* fatalSignals = kssignal_fatalSignals();int fatalSignalsCount = kssignal_numFatalSignals();if(g_previousSignalHandlers == NULL){g_previousSignalHandlers = (struct sigaction *)malloc(sizeof(*g_previousSignalHandlers)* (unsigned)fatalSignalsCount);}//初始化处理函数结构体struct sigaction action = {{0}};action.sa_flags = SA_SIGINFO | SA_ONSTACK;sigemptyset(&action.sa_mask);action.sa_sigaction = &signalExceptionHandler;for(int i = 0; i < fatalSignalsCount; i++){if(sigaction(fatalSignals[i], &action, &g_previousSignalHandlers[i]) != 0){// 取消已监听的handlerfor(i--;i >= 0; i--){sigaction(fatalSignals[i], &g_previousSignalHandlers[i], NULL);}break;}}
}
void uninstallSignalHandler(void){const int* fatalSignals = kssignal_fatalSignals();int fatalSignalsCount = kssignal_numFatalSignals();for(int i = 0; i < fatalSignalsCount; i++){sigaction(fatalSignals[i], &g_previousSignalHandlers[i], NULL);}
} 防崩溃处理
常见崩溃类型
根据笔者经验来看,oc中大部分崩溃都是源于没有对调用方法进行入参判断,比如数组添加object没有判空,访问数组元素越界等;还有一些C++崩溃,比如使用野指针。
防崩溃处理方案
由于oc中大部分崩溃都是来源于未对入参进行判断,所以调用方法对入参进行判断就能解决崩溃。如何统一地解决这类崩溃,有两种方案:hook方案和安全接口
hook方案
该方案对系统常见类的方法进行hook,进行入参判断。比如对hook NSMutableArray的addObject方法,进行判空操作。
@implementation NSMutableArray (akSafe)
+ (void)load {[self swizzMethodOriginalSelector:@selector(addObject:)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) {class_replaceMethod(self.class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));} else {method_exchangeImplementations(originalMethod, swizzledMethod);}
}
- (void)aksafe_AddObject:(id)anObject {if (anObject) {[self aksafe_AddObject:anObject];}
}
@end 安全接口
该方案对系统常见类的方法进行一层封装,进行入参判断。大家统一调用安全接口,比如封装NSMutableArray的addObject方法为aksafe_AddObject,大家统一调用aksafe_AddObject添加对象。
@implementation NSMutableArray (aksafe)
- (void)aksafe_AddObject:(id)anObject {if (anObject) {[self addObject:anObject];}
}
@end 两种方案各有优缺点,hook方案优点是业务方直接调用系统方法就行,缺点是由于要进行hook,有损性能;安全接口方案是业务方要统一调用安全接口,优点则是轻量。笔者推荐方案二,轻量并且可以作为编码规范。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
