????????在開發(fā)中遇到Crash是很正常的現(xiàn)象,還記得剛?cè)雐OS開發(fā)這個(gè)坑的時(shí)候根本不懂什么、錯(cuò)誤提示啊、函數(shù)調(diào)用棧啊、po命令調(diào)試啊等等..........。那個(gè)時(shí)候最長用的就是異常斷點(diǎn),還有就是N個(gè)斷點(diǎn)一步一步往下走哈哈哈直到遇到異常。????????
????????可是最近對接SDK比較多有時(shí)候遇到偶現(xiàn)的Crash自己手賤又沒有把輸出Log Copy出來就很尷尬了,于是想著是否可以把APP運(yùn)行時(shí)的Crash Log記錄下來這樣下次連上電腦就可以直接查看上次的Crash日志進(jìn)行錯(cuò)誤分析了。于是百度了下發(fā)現(xiàn)Apple已經(jīng)為我們提供了一個(gè)API在異常拋出之前進(jìn)行調(diào)用,而這個(gè)API函數(shù)參數(shù)就是一個(gè)回調(diào)函數(shù)。下面我們來使用這個(gè)API進(jìn)行一個(gè)簡單的日志記錄上報(bào)的單例類實(shí)現(xiàn)。
ACCrashManager?.h的實(shí)現(xiàn)
typedefvoid(^ACReportBlock)(BOOLshouldReport);
@interface ACCrashManager :NSObject
@property (nonatomic, strong, readonly) NSData *crashData;
+ (instancetype)shareManager;
- (void)startCacheCrashWith:(NSString*)APPId
? ? ? ? ? ? withReportBlock:(ACReportBlock)reportBlock;
@end
.m的實(shí)現(xiàn)
#define ACFileManager? [NSFileManager defaultManager]
#define ACFileHandleWith(filePath) [NSFileHandle fileHandleForWritingAtPath:filePath]
@interface ACCrashManager ()
@property (nonatomic, strong) NSData *crashData;
@property (nonatomic, copy) NSString *crashAPPId;
@property (nonatomic, copy) ACReportBlock reportBlock;
@end
@implementation ACCrashManager
#pragma mark - Create Manager
static ACCrashManager*crashManager =nil;
+ (instancetype)shareManager {
? ? staticdispatch_once_tonceToken;
? ? dispatch_once(&onceToken, ^{
? ? ? ? crashManager = [[ACCrashManager alloc] init];
? ? });
? ? return crashManager;
}
#pragma mark - Setter && getter
- (NSData*)crashData
{
? ? NSString*filePath =ACCrashFilePath();
? ? if([ACFileManagerfileExistsAtPath:filePath])
? ? {
? ? ? ? return [NSData dataWithContentsOfFile:filePath];
? ? }else
? ? {
? ? ? ? returnnil;
? ? }
}
#pragma mark - Public Method
- (void)startCacheCrashWith:(NSString*)APPId
? ? ? ? ? ? withReportBlock:(ACReportBlock)reportBlock
{
? ? self.reportBlock= reportBlock;
? ? NSSetUncaughtExceptionHandler(&ACUncaughtExceptionHandler);//Apple異常調(diào)用API
? ? NSLog(@"AC : Crach Start Cache");
? ? self.crashAPPId= APPId.length>0? APPId :@"AC.ErrorDir";
? ? ? ? if(self.reportBlock)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? self.reportBlock(YES);
? ? ? ? ? ? }
}
#pragma mark - Private Method
/*捕獲錯(cuò)誤異常的回調(diào)函數(shù)*/
void ACUncaughtExceptionHandler(NSException*exception)//異常調(diào)用API的回調(diào)函數(shù)
{
? ? NSString*reportErrorStr =? [NSString stringWithFormat:@"\n\n\"ERROR\" :{\n\"AC Crash TIME\" : \"%@\",\n\"AC Crash Name\" : \"%@\", \n\"AC Crash Reason\" : \"%@\", \n\n\"AC Crash CallStackReturnAddresses\" : \n\"%@\", \n\"AC Crash CallStackSymbols\" : \n\"%@\" \n}", ACGetTomeNow(), exception.name, exception.reason, exception.callStackReturnAddresses, exception.callStackSymbols];
? ? NSData *reportData = [reportErrorStr dataUsingEncoding:NSUTF8StringEncoding];
? ? NSString*filePath =ACCrashFilePath();
? ? BOOLisWrite =NO;
? ? if([ACFileManagerfileExistsAtPath:filePath])
? ? {
? ? ? ? isWrite =YES;
? ? ? ? NSFileHandle*fileHandle =ACFileHandleWith(filePath);
? ? ? ? [fileHandleseekToEndOfFile];
? ? ? ? [fileHandlewriteData:reportData];
? ? ? ? [fileHandlesynchronizeFile];
? ? ? ? [fileHandlecloseFile];
? ? }else
? ? {
? ? ? ? isWrite = [ACFileManagercreateFileAtPath:filePathcontents:reportDataattributes:nil];
? ? }
? ? if(isWrite)
? ? {
? ? ? ? NSLog(@"文件寫入成功");
? ? }else
? ? {
? ? ? ? NSLog(@"文件寫入失敗");
? ? }
}
/*創(chuàng)建錯(cuò)誤日志文件夾*/
staticNSString*ACReportFileDirectories(void)
{
? ? NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
? ? NSString *reportPath = [documentPath stringByAppendingString:[NSString stringWithFormat:@"/%@", crashManager.crashAPPId]];
? ? BOOLisDir =YES;
? ? if(![ACFileManagerfileExistsAtPath:reportPathisDirectory:&isDir])
? ? {
? ? ? ? NSError*error =nil;
? ? ? ? BOOL isCreateDir = [[NSFileManager defaultManager] createDirectoryAtPath:reportPath withIntermediateDirectories:YES attributes:nil error:&error];
? ? ? ? if(isCreateDir)
? ? ? ? {
? ? ? ? ? ? NSLog(@"文件夾創(chuàng)建成功");
? ? ? ? }else
? ? ? ? {
? ? ? ? ? ? NSLog(@"文件夾創(chuàng)建失敗 : %@", error);
? ? ? ? }
? ? }
? ? returnreportPath;
}
/* 獲取錯(cuò)誤文件存放的文件夾 */
staticNSString*ACCrashFilePath(void)
{
? ? NSString *filePath = ACReportFileDirectories();
? ? filePath = [filePathstringByAppendingString:@"/ACErrorReport"];
? ? returnfilePath;
}
/* 獲取當(dāng)前時(shí)間*/
staticNSString*ACGetTomeNow()
{
? ? NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
? ? [formattersetDateFormat:@"YYYY-MM-dd HH:mm:ss"];
? ? NSDate*datenow = [NSDatedate];
? ? NSString*currentTimeString = [formatterstringFromDate:datenow];
? ? returncurrentTimeString;
}
這樣便可以實(shí)現(xiàn)一個(gè)簡單的Crash日志記錄的功能。如果需要上傳日志只需要在回調(diào)reportBlock中判斷是否有上傳日志,如果有則將crashData上傳服務(wù)器。這里日志格式可以根據(jù)服務(wù)器要求自定義。????????????我這里寫的JSON。不過callStackReturnAddresses、callStackSymbols沒有json格式化所以好像報(bào)錯(cuò)、需要上傳服務(wù)器的自己再修改下reportErrorStr格式就好。
當(dāng)然這里只是簡單的一個(gè)實(shí)現(xiàn)思路、我們還是可以使用更好的日志收集分析工具的,如Bugly官方地址,這里不多介紹因?yàn)?b>Bugly的使用相對來說很簡單,而且日志分析也是可視化的、最最主要的他還提供了一些推薦的解決方案。是不是以后不用改BUG了????????????好啦、到此結(jié)束。開啟Bugly之旅啦!!!