參考博文:http://www.cnblogs.com/easonoutlook/archive/2012/12/27/2835979.html
開發程序的過程中不管我們已經如何小心,總是會在不經意間遇到程序閃退。流暢的操作被無情地Crash打斷,當程序運行Crash的時候,系統會把運行的最後時刻的運行信息記錄下來,存儲到一個文件中,也就是我們所說的Crash文件,當時如果是真機測試離開Xcode的時候Crash掉,我們是無法知道crash的具體位置的。現在做一個程序統一記錄crash的位置。先科普一下crash的知識:
Crash文件結構
1、Process Information(進程信息)
Incident Idnetifier | 崩潰報告的唯一標識符,不同的Crash |
CrashReporter Key | 設備標識相對應的唯一鍵值(並非真正的設備的UDID,蘋果爲了保護用戶隱私iOS6以後已經無法獲取)。通常同一個設備上同一版本的App發生Crash時,該值都是一樣的。 |
Hardware Model | 代表發生Crash的設備類型,上圖中的“iPad4,4”代表iPad Air |
Process | 代表Crash的進程名稱,通常都是我們的App的名字, []裏面是當時進程的ID |
Path | 可執行程序在手機上的存儲位置,注意路徑時到XXX.app/XXX,XXX.app其實是作爲一個Bundle的,真正的可執行文件其實是Bundle裏面的XXX,感興趣的可以自己查一下相關資料,有機會我後面也會介紹到 |
Identifier | 你的App的Indentifier,通常爲“com.xxx.yyy”,xxx代表你們公司的域名,yyy代表某一個App |
Version | 當前App的版本號,由Info.plist中的兩個字段組成,CFBundleShortVersionString and CFBundleVersion |
Code Type | 當前App的CPU架構 |
Parent Process | 當前進程的父進程,由於iOS中App通常都是單進程的,一般父進程都是launchd |
2、Basic Information
Date/Time | Crash發生的時間,可讀的字符串 |
OS Version | 系統版本,()內的數字代表的時Bulid號 |
Report Version | Crash日誌的格式,目前基本上都是104,不同的version裏面包含的字段可能有不同 |
3、Exception(非常重要)
Exception Type | 異常類型 |
Exception Subtype: | 異常子類型 |
Crashed Thread | 發生異常的線程號 |
4、Thread Backtrace
發生Crash的線程的Crash調用棧,從上到下分別代表調用順序,最上面的一個表示拋出異常的位置,依次往下可以看到API的調用順序。上圖的信息表明本次Crash出現xxxViewController的323行,出錯的函數調用爲orderCountLoadFailed。
5、Thread State
Crash時發生時刻,線程的狀態,通常我們根據Crash棧即可獲取到相關信息,這部分一般不用關心。
6、Binary Images
Crash時刻App加載的所有的庫,其中第一行是Crash發生時我們App可執行文件的信息,可以看出爲armv7,可執行文件的包得uuid位c0f……cd65,解析Crash的時候dsym文件的uuid必須和這個一樣才能完成Crash的符號化解析。
廢話說了那麼多,到底怎麼捕獲整個系統的crash呢,入正題,其實也很簡單:創建一個工具類 ExceptionHandler.h 和 ExceptionHandler.m(oc版本),swift版本的下面再講.
ExceptionHandler.h
#import <UIKit/UIKit.h>
@interface ExceptionHandler : NSObject{
BOOL dismissed;
}
@end
void HandleException(NSException *exception);
void SignalHandler(int signal);
void InstallUncaughtExceptionHandler(void);
ExceptionHandler.m
#import "ExceptionHandler.h"
#include <libkern/OSAtomic.h>
#include <execinfo.h>
NSString * const ExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName";
NSString * const ExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey";
NSString * const ExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey";
volatile int32_t UncaughtExceptionCount = 0;
const int32_t UncaughtExceptionMaximum = 10;
const NSInteger ExceptionHandlerSkipAddressCount = 4;
const NSInteger ExceptionHandlerReportAddressCount = 5;
@implementation ExceptionHandler
+ (NSArray *)backtrace
{
void * callstack[128];
int frames = backtrace(callstack, 128);
char **strs = backtrace_symbols(callstack, frames);
int i;
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (
i = ExceptionHandlerSkipAddressCount;
i < ExceptionHandlerSkipAddressCount +
ExceptionHandlerReportAddressCount;
i++)
{
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
- (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex
{
if (anIndex == 0)
{
dismissed = YES;
}
}
- (void)validateAndSaveCriticalApplicationData
{
}
- (void)handleException:(NSException *)exception
{
[self validateAndSaveCriticalApplicationData];
UIAlertView *alert =
[[UIAlertView alloc]
initWithTitle:NSLocalizedString(@"Unhandled exception", nil)
message:[NSString stringWithFormat:NSLocalizedString(
@"You can try to continue but the application may be unstable.\n\n"
@"Debug details follow:\n%@\n%@", nil),
[exception reason],
[[exception userInfo] objectForKey:ExceptionHandlerAddressesKey]]
delegate:self
cancelButtonTitle:NSLocalizedString(@"Quit", nil)
otherButtonTitles:NSLocalizedString(@"Continue", nil), nil];
[alert show];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!dismissed)
{
for (NSString *mode in (NSArray *)allModes)
{
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
CFRelease(allModes);
NSSetUncaughtExceptionHandler(NULL);
signal(SIGABRT, SIG_DFL);
signal(SIGILL, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
if ([[exception name] isEqual:ExceptionHandlerSignalExceptionName])
{
kill(getpid(), [[[exception userInfo] objectForKey:ExceptionHandlerSignalKey] intValue]);
}
else
{
[exception raise];
}
}
@end
void HandleException(NSException *exception)
{
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum)
{
return;
}
NSArray *callStack = [ExceptionHandler backtrace];
NSMutableDictionary *userInfo =
[NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo
setObject:callStack
forKey:ExceptionHandlerAddressesKey];
[[[ExceptionHandler alloc] init]
performSelectorOnMainThread:@selector(handleException:)
withObject:
[NSException
exceptionWithName:[exception name]
reason:[exception reason]
userInfo:userInfo]
waitUntilDone:YES];
}
void SignalHandler(int signal)
{
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum)
{
return;
}
NSMutableDictionary *userInfo =
[NSMutableDictionary
dictionaryWithObject:[NSNumber numberWithInt:signal]
forKey:ExceptionHandlerSignalKey];
NSArray *callStack = [ExceptionHandler backtrace];
[userInfo
setObject:callStack
forKey:ExceptionHandlerAddressesKey];
[[[ExceptionHandler alloc] init]
performSelectorOnMainThread:@selector(handleException:)
withObject:
[NSException
exceptionWithName:ExceptionHandlerSignalExceptionName
reason:
[NSString stringWithFormat:
NSLocalizedString(@"Signal %d was raised.", nil),
signal]
userInfo:
[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:ExceptionHandlerSignalKey]]
waitUntilDone:YES];
}
void InstallUncaughtExceptionHandler(void)
{
NSSetUncaughtExceptionHandler(&HandleException);
signal(SIGABRT, SignalHandler);
signal(SIGILL, SignalHandler);
signal(SIGSEGV, SignalHandler);
signal(SIGFPE, SignalHandler);
signal(SIGBUS, SignalHandler);
signal(SIGPIPE, SignalHandler);
}
使用方式:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
InstallUncaughtExceptionHandler();
return YES;
}
大功告成
swift版本:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//輸出bug信息
print("系統bug日誌記錄---------------------\(NSUserDefaults.standardUserDefaults().valueForKey(ERROR_MESSAGE))")
NSSetUncaughtExceptionHandler { exception in
var message = exception.callStackSymbols
message.removeAll()
message.append("錯誤理由:\(exception.reason!)")
message.append("錯誤詳細信息:\(exception.callStackSymbols)")
NSUserDefaults.standardUserDefaults().setObject(message, forKey: ERROR_MESSAGE)
NSUserDefaults.standardUserDefaults().synchronize()
print("系統bug日誌記錄---------------------\n\(message)")
}
return true
}
swift比oc簡單很多,直接在didFinishLaunchingWithOptions加入上面的代碼就可以。
測試代碼:
NSMutableArray *array = [NSMutableArray array];
[array addObject:@"1"];
[array addObject:@"2"];
NSLog(@"%@",array[9]);
效果圖 數組越界: