????????在開(kāi)發(fā)中遇到Crash是很正常的現(xiàn)象,還記得剛?cè)雐OS開(kāi)發(fā)這個(gè)坑的時(shí)候根本不懂什么糊治、錯(cuò)誤提示啊、函數(shù)調(diào)用棧啊缺前、po命令調(diào)試啊等等..........。那個(gè)時(shí)候最長(zhǎng)用的就是異常斷點(diǎn)竣付,還有就是N個(gè)斷點(diǎn)一步一步往下走哈哈哈直到遇到異常诡延。????????
????????可是最近對(duì)接SDK比較多有時(shí)候遇到偶現(xiàn)的Crash自己手賤又沒(méi)有把輸出Log Copy出來(lái)就很尷尬了滞欠,于是想著是否可以把APP運(yùn)行時(shí)的Crash Log記錄下來(lái)這樣下次連上電腦就可以直接查看上次的Crash日志進(jìn)行錯(cuò)誤分析了古胆。于是百度了下發(fā)現(xiàn)Apple已經(jīng)為我們提供了一個(gè)API在異常拋出之前進(jìn)行調(diào)用,而這個(gè)API函數(shù)參數(shù)就是一個(gè)回調(diào)函數(shù)筛璧。下面我們來(lái)使用這個(gè)API進(jìn)行一個(gè)簡(jiǎn)單的日志記錄上報(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(@"文件寫(xiě)入成功");
? ? }else
? ? {
? ? ? ? NSLog(@"文件寫(xiě)入失敗");
? ? }
}
/*創(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è)簡(jiǎn)單的Crash日志記錄的功能。如果需要上傳日志只需要在回調(diào)reportBlock中判斷是否有上傳日志夭谤,如果有則將crashData上傳服務(wù)器棺牧。這里日志格式可以根據(jù)服務(wù)器要求自定義。????????????我這里寫(xiě)的JSON朗儒。不過(guò)callStackReturnAddresses颊乘、callStackSymbols沒(méi)有json格式化所以好像報(bào)錯(cuò)、需要上傳服務(wù)器的自己再修改下reportErrorStr格式就好醉锄。
當(dāng)然這里只是簡(jiǎn)單的一個(gè)實(shí)現(xiàn)思路乏悄、我們還是可以使用更好的日志收集分析工具的,如Bugly官方地址恳不,這里不多介紹因?yàn)?b>Bugly的使用相對(duì)來(lái)說(shuō)很簡(jiǎn)單檩小,而且日志分析也是可視化的、最最主要的他還提供了一些推薦的解決方案烟勋。是不是以后不用改BUG了????????????好啦规求、到此結(jié)束。開(kāi)啟Bugly之旅啦B训搿W柚住!