Categories
程式開發

iOS性能優化— 一、crash監控及防崩潰處理


大家好,歡迎來到iOS性能優化

本篇文章將為大家講解下crash監控及防崩潰處理。

如何收集crash利用bugly、友盟等第三方收集監控crash原理防崩潰處理常見崩潰類型防崩潰處理方案hook方案安全接口

如何收集crash

在平常開發過程中,由於代碼的不嚴謹比如不對入參做校驗,使用C++野指針等會造成程序crash。 crash應該算是最嚴重的bug了,尤其是線上crash,如果App用戶量大的話可能造成很大的影響,所以需要有一套機制來收集項目中的crash並及時解決。

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

大部分公司都是採用第三方平台來收集crash。業內用的比較多有bugly、友盟、talkingdata。筆者比較推薦bugly,騰訊研發,比較輕量,用來監控crash和卡頓還是很方便的。

監控crash原理

一線大廠,大部分都會自研crash捕獲框架。這個時候了解crash捕獲原理就很有必要了,大家可以閱讀開源庫崩潰“或者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:@"%srn", 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 = 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"來獲取一份詳細的大廠面試資料為你的跳槽多添一份保障。

iOS性能優化— 一、crash監控及防崩潰處理 1