獲取IOS應用異常崩潰日誌資訊
應用異常崩潰是很正常的事情,但是應用異常崩潰資訊對開發者非常重要。下面就介紹如何在iOS應用中捕獲異常崩潰資訊:
1. 程式啟動中新增異常捕獲監聽函式,用來獲取異常資訊
NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler);
官方文件介紹:Sets the top-level error-handling function where you can perform last-minute logging before the program terminates.
UncaughtExceptionHandler是一個函式指標型別,所指向的函式需要我們實現,可以取自己想要的名字。當程式發生異常崩潰時,該函式會得到呼叫,這跟C,C++中的回撥函式的概念是一樣的。
2. 實現自己的處理函式
void UncaughtExceptionHandler(NSException *exception) {
NSArray *arr = [exception callStackSymbols];//得到當前呼叫棧資訊
NSString *reason = [exception reason];//非常重要,就是崩潰的原因
NSString *name = [exception name];//異常型別
NSLog(@"exception type : %@ \n crash reason : %@ \n call stack info : %@", name, reason, arr);
}
3. 侷限
新增異常捕獲監聽函式,只能監聽NSException型別的異常。eg:
NSDictionary *userInfo = [[NSDictionary alloc]initWithObjectsAndKeys:@"info1", @"key1", nil];
NSException *exception = [[NSException alloc]initWithName:@"自定義異常" reason:@"自定義異常原因" userInfo:userInfo];
@throw exception;
而引起崩潰的大多數原因如:記憶體訪問錯誤,重複釋放等錯誤就無能為力了。因為這種錯誤它丟擲的是Signal,所以必須要專門做Signal處理, 可以參考如下封裝;測試時,可以呼叫abort
//
// MQLSignalHandler.h
// WebViewJS
//
// Created by MQL on 16/4/23.
// Copyright © 2016年 MQL. All rights reserved.
//
#import <Foundation/Foundation.h>
#include <sys/signal.h>
@interface MQLSignalHandler : NSObject
/**
* 訊號處理器單例獲取
*
* @return 訊號處理器單例
*/
+ (instancetype)instance;
/**
* 處理異常用到的方法
*
* @param exception 自己封裝的異常物件
*/
- (void)handleExceptionTranslatedFromSignal:(NSException *)exception;
@end
//
// MQLSignalHandler.m
// WebViewJS
//
// Created by MQL on 16/4/23.
// Copyright © 2016年 MQL. All rights reserved.
//
#import "MQLSignalHandler.h"
#import <UIKit/UIKit.h>
#include <libkern/OSAtomic.h>
#include <execinfo.h>
//當前處理的異常個數
volatile int32_t UncaughtExceptionCount = 0;
//最大能夠處理的異常個數
volatile int32_t UncaughtExceptionMaximum = 10;
/**
* 捕獲訊號後的回撥函式
*
* @param signo 訊號變數
*/
void callbackHandlerOfCatchedSignal(int signo)
{
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum)
{
return;
}
NSMutableDictionary *userInfo =[NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signo] forKey:@"signal"];
//建立一個OC異常物件
NSException *ex = [NSException exceptionWithName:@"SignalExceptionName" reason:[NSString stringWithFormat:@"Signal %d was raised.\n",signo] userInfo:userInfo];
//處理異常訊息
[[MQLSignalHandler instance] performSelectorOnMainThread:@selector(handleExceptionTranslatedFromSignal:) withObject:ex waitUntilDone:YES];
}
@interface MQLSignalHandler ()
@property BOOL isDismissed;
/**
* 註冊訊號處理器
*/
- (void)registerSignalHandler;
@end
@implementation MQLSignalHandler
/**
* 訊號處理器單例獲取
*
* @return 訊號處理器單例
*/
+ (instancetype)instance{
static dispatch_once_t onceToken;
static MQLSignalHandler *s_SignalHandler = nil;
dispatch_once(&onceToken, ^{
if (s_SignalHandler == nil) {
s_SignalHandler = [[MQLSignalHandler alloc] init];
[s_SignalHandler registerSignalHandler];
}
});
return s_SignalHandler;
}
/**
* 註冊訊號處理器
*/
- (void)registerSignalHandler
{
//註冊程式由於abort()函式呼叫發生的程式中止訊號
signal(SIGABRT, callbackHandlerOfCatchedSignal);
//註冊程式由於非法指令產生的程式中止訊號
signal(SIGILL, callbackHandlerOfCatchedSignal);
//註冊程式由於無效記憶體的引用導致的程式中止訊號
signal(SIGSEGV, callbackHandlerOfCatchedSignal);
//註冊程式由於浮點數異常導致的程式中止訊號
signal(SIGFPE, callbackHandlerOfCatchedSignal);
//註冊程式由於記憶體地址未對齊導致的程式中止訊號
signal(SIGBUS, callbackHandlerOfCatchedSignal);
//程式通過埠傳送訊息失敗導致的程式中止訊號
signal(SIGPIPE, callbackHandlerOfCatchedSignal);
}
//處理異常用到的方法
- (void)handleExceptionTranslatedFromSignal:(NSException *)exception
{
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"程式出現問題啦" message:@"崩潰資訊" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:nil];
[alertView show];
//當接收到異常處理訊息時,讓程式開始runloop,防止程式死亡
while (!_isDismissed) {
for (NSString *mode in (__bridge NSArray *)allModes)
{
CFRunLoopRunInMode((CFStringRef)mode, 0, true);
}
}
//當點選彈出檢視的Cancel按鈕哦,isDimissed = YES,上邊的迴圈跳出
CFRelease(allModes);
//攔截處理後,執行預設處理
signal(SIGABRT, SIG_DFL);
signal(SIGILL, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
}
- (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex
{
//因為這個彈出檢視只有一個Cancel按鈕,所以直接進行修改isDimsmissed這個變量了
_isDismissed = YES;
}
@end