1. 程式人生 > >獲取IOS應用異常崩潰日誌資訊

獲取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

()函式,模擬傳送SIGABRT訊號,不要聯機測試,要離線測試。

//
//  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