19

iOS 性能优化 — 一、crash 监控及防崩溃处理

 3 years ago
source link: https://xie.infoq.cn/article/f3d1fffff5bc7141ea505eb9c
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

大家好,欢迎来到 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处理器里面, 需要手动调用下原来的处理器

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)
{
// 取消已监听的handler
for(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,有损性能;安全接口方案是业务方要统一调用安全接口,优点则是轻量。 笔者推荐方案二,轻量并且可以作为编码规范。

资料推荐

如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群 1012951431 来获取一份详细的大厂面试资料为你的跳槽多添一份保障。

6FFBry2.png!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK